#include "rtc_manager.h"

#include <chrono>
#include <cstdint>
#include <cstring>
#include <stdio.h>
#include <list>
#include <string>
#include <gio/gio.h>
#include <glib.h>
#include <netinet/in.h>
#include <agent.h>
#include <rapidjson/document.h>
#include <rapidjson/writer.h>
#include <rapidjson/prettywriter.h>
#include <rapidjson/stringbuffer.h>

#include "codecs.hpp"
#include "mixer_manager.hpp"
#include "push_to_talk.hpp"
#include "proxy.hpp"
#include "remote_connector.hpp"
#include "rtc_recorder.h"
#include "xrtc_endpoint.h"
#include "xplugin_rtc_debug.h"
#include "http_parser.h"
#include "xhttp_str.h"
#include "xm_app_config.h"
#include "xm_log.h"
#include "xm_crash.h"
#include "xm_version.h"
#include "xmixer.h"
#include "xcutil.h"



#define rmgr_dbgi(FMT, ARGS...) dbgi("|%s| " FMT, "rtcmgr", ##ARGS )
#define rmgr_dbge(FMT, ARGS...) dbge("|%s| " FMT, "rtcmgr", ##ARGS )

#define ZERO_ALLOC(o, type, sz) do{o=(type)malloc(sz); memset(o, 0, sz);}while(0)

using namespace rapidjson;


struct rtc_manager_st{
    GMainContext * main_context;
    std::list<rtc_endpoint_t> * rtc_list;
    std::map<std::string, rtc_endpoint_t> * rtc_map;
    uint64_t next_rtc_id;
    xplugin_rtc_ops * switcher;
    xhttp_str_t http_notifier;
    GSource * timer;
    char work_str[4096];
    // char work_str2[4096];
    rtc_recorder_t rec;
    void* mixer_mgr;
    proxy* pr;
    remote_connector_manager* rcm;
};

static inline void rtcmgr_delete_rtc(rtc_manager_t obj, rtc_endpoint_t rtc);
static inline void rtcmgr_check_rtc_timeout(rtc_manager_t obj);
static gboolean rtcmgr_timer_handler(gpointer user_data);

rtc_manager_t rtcmgr_create(){
    rtc_manager_t obj = NULL;
    ZERO_ALLOC(obj, rtc_manager_t, sizeof(struct rtc_manager_st));
    obj->rtc_list = new std::list<rtc_endpoint_t>;
    obj->rtc_map = new std::map<std::string, rtc_endpoint_t>;
    obj->switcher = xplugin_rtc_switcher_instance();
    obj->http_notifier = xhttp_str_create();
    
    //obj->timer = g_timeout_source_new(2000);
    obj->timer = g_timeout_source_new(500);
    g_source_set_callback(obj->timer, rtcmgr_timer_handler, obj, NULL);
    g_source_attach(obj->timer, obj->main_context);
    app_config * config = app_config_get();
    obj->rec = rtc_recorder_new(config->job_path, config->tlv_path);
    obj->mixer_mgr = mixer_manager_new(obj->main_context, obj->rec);
    char* url = g_strdup_printf("%s:%d", config->http_ip, config->http_port);
    obj->pr = proxy_new(obj->main_context, config->proxy_addr, url);
    obj->rcm = rcm_new(obj->main_context);

    g_free(url);
    return obj;
}

void rtcmgr_delete(rtc_manager_t obj){
    if(!obj) return ;
    
    if(obj->timer != NULL) {
        g_source_destroy(obj->timer);
        g_source_unref(obj->timer);
        obj->timer = NULL;
    }
    
    if(obj->switcher){
        if(obj->switcher->on_destroy){
            obj->switcher->on_destroy(obj->switcher->context);
        }
        obj->switcher = NULL;
    }
    
    if(obj->rtc_list){
        // TODO: delete rtc here
        delete obj->rtc_list;
        obj->rtc_list = NULL;
    }
    if(obj->rtc_map){
        for(std::map<std::string, rtc_endpoint_t>::iterator iter = obj->rtc_map->begin(); iter != obj->rtc_map->end();) {
            rtc_endpoint_t rtc = iter->second;
            obj->rtc_map->erase(iter++);
            rtcmgr_delete_rtc(obj, rtc);
        }
        delete obj->rtc_map;
        obj->rtc_map  =NULL;
    }
    
    if(obj->http_notifier){
        xhttp_str_delete(obj->http_notifier);
        obj->http_notifier = NULL;
    }

    if (obj->rec) {
        rtc_recorder_delete(obj->rec);
        obj->rec = nullptr;
    }

    if (obj->mixer_mgr) {
        mixer_manager_delete(obj->mixer_mgr);
        obj->mixer_mgr = nullptr;
    }

    if (obj->pr) {
        proxy_delete(obj->pr);
        obj->pr = nullptr;
    }

    if (obj->rcm) {
        rcm_del(obj->rcm);
        obj->rcm = nullptr;
    }
    free(obj);
}

int rtcmgr_set_candidate(rtc_manager_t obj, rtc_endpoint_t rtc, const Value &cand_doc, int k){
    int ret = -1;
    do{
        Value::ConstMemberIterator it_mid = cand_doc.FindMember("mid");
        Value::ConstMemberIterator it_cand = cand_doc.FindMember("candidate");
        Value::ConstMemberIterator it_midx = cand_doc.FindMember("mlineindex");
        
        if(it_mid == cand_doc.MemberEnd()){
            it_mid = cand_doc.FindMember("sdpMid");
        }
        
        if(it_cand == cand_doc.MemberEnd()){
            it_cand = cand_doc.FindMember("sdp");
        }
        
        if(it_midx == cand_doc.MemberEnd()){
            it_midx = cand_doc.FindMember("sdpMLineIndex");
        }
        
        
        if (it_mid == cand_doc.MemberEnd() || it_cand == cand_doc.MemberEnd() || it_midx == cand_doc.MemberEnd()){
            rmgr_dbge("No.%d candidate are non-exist some field", k);
            ret = -1;
            break;
        }
        
        if(!it_mid->value.IsString() || !it_cand->value.IsString() || !it_midx->value.IsInt()){
            rmgr_dbge("parse No.%d candidate type fail", k);
            ret = -1;
            break;
        }
        
        
        std::string cand_sdp = "a=" + std::string(it_cand->value.GetString(), it_cand->value.GetStringLength());
        
        ret = rtc_endpoint_add_remote_candidate(rtc, it_midx->value.GetInt(), it_mid->value.GetString(), cand_sdp.c_str(), 1);
        if(ret){
            rmgr_dbge("fail to add candidate sdp: %s", cand_sdp.c_str());
            ret = -1;
            break;
        }
        ret = 0;
    }while(0);
    return ret;
}

int rtcmgr_maybe_set_candidates(rtc_manager_t obj, rtc_endpoint_t rtc, const Value &req){
    int ret = 0;
    Value::ConstMemberIterator it_candidates = req.FindMember("candidates");
    if(it_candidates != req.MemberEnd() && it_candidates->value.IsArray()){
        // {"candidate":"candidate:1 1 UDP 2013266431 172.17.1.58 49644 typ host","mid":"audio","mlineindex":0,"seq":1,"type":"candidate"}
        
        ret = 0;
        for(size_t k=0 ; k< it_candidates->value.Size(); ++k){
            
            if(it_candidates->value[k].IsString()){
                Document cand_doc;
                cand_doc.Parse(it_candidates->value[k].GetString());
                if(!cand_doc.IsObject()){
                    rmgr_dbge("parse No.%lu candidate json fail", k);
                    ret = -1;
                    break;
                }
                
                ret = rtcmgr_set_candidate(obj, rtc, cand_doc, k);
                if(ret) break;
            }else if(it_candidates->value[k].IsObject()){
                ret = rtcmgr_set_candidate(obj, rtc, it_candidates->value[k], k);
                if(ret) break;
            }else{
                rmgr_dbge("parse No.%lu candidate unknown type", k);
                ret = -1;
                break;
            }
        }
//        if(ret) break;
    }
    return ret;
}

static inline
void rtcmgr_check_rtc_timeout(rtc_manager_t obj){
    int64_t now = get_timestamp_ms();
    for(std::map<std::string, rtc_endpoint_t>::iterator iter = obj->rtc_map->begin(); iter != obj->rtc_map->end();) {
        rtc_endpoint_t rtc = iter->second;
        if(!rtc_endpoint_check_live(rtc, now)) {
            obj->rtc_map->erase(iter++);
            rtcmgr_delete_rtc(obj, rtc);
            continue;
        }
        iter++;
    }
    rtc_recorder_check_timeout(obj->rec, (uint64_t)now);

}

static
gboolean rtcmgr_timer_handler(gpointer user_data){
    rtc_manager_t obj = (rtc_manager_t) user_data;
    rtcmgr_check_rtc_timeout(obj);
    return TRUE;
}

static inline
rtc_endpoint_t rtcmgr_get_rtc(rtc_manager_t obj,  const char * rtc_id, int is_remove){
    std::map<std::string, rtc_endpoint_t>::iterator it = obj->rtc_map->find(rtc_id);
    if (it == obj->rtc_map->end()){
        return NULL;
    }
    
    rtc_endpoint_t rtc = it->second;
    if(is_remove){
        obj->rtc_map->erase(it);
    }
    
    return rtc;
}

static inline
void rtcmgr_delete_rtc(rtc_manager_t obj, rtc_endpoint_t rtc) {
    rtc_endpoint_delete(rtc);
}

static inline
int rtcmgr_remove_rtc(rtc_manager_t obj, const char * rtc_id){
    int ret = -1;
    do{
        rtc_endpoint_t rtc = rtcmgr_get_rtc(obj, rtc_id, 1);
        if(!rtc){
            rmgr_dbge("NOT found rtcId %s", rtc_id);
            ret = -1;
            break;
        }
        
        rtcmgr_delete_rtc(obj, rtc);
        rmgr_dbgi("rmgr delete rtcId %s", rtc_id);

        
        ret = 0;
    }while(0);
    
    return ret;
}

static
int rtcmgr_set_rtc_cburl(rtc_manager_t obj, rtc_endpoint_t rtc, const char * url, int url_len, const char * from_ip, int from_port){
    int ret = -1;
    do{
        const char * host = NULL;
        int host_len;
        int port;
        const char * path = NULL;
        int path_len;
        
        if(url && url_len == 0){
            url_len = strlen(url);
        }
        
        if(*url == '/'){
            // TODO: support default cburl in configure
            ret = -1;
            break;
            
        }else{
            struct http_parser_url u;
            int is_connect = 0;
            ret = http_parser_parse_url(url, url_len, is_connect, &u);
            if(ret){
                rmgr_dbge("fail to parse cburl [%.*s]", url_len, url);
                ret = -1;
                break;
            }
            
            if( !(u.field_set & (1 << UF_HOST))){
                rmgr_dbge("no HOST in cburl [%.*s]", url_len, url);
                ret = -1;
                break;
            }
            
            host = url + u.field_data[UF_HOST].off;
            host_len = u.field_data[UF_HOST].len;
            port = u.port;
            
            if( (u.field_set & (1 << UF_PATH)) ) {
                path = url + u.field_data[UF_PATH].off;
                path_len = u.field_data[UF_PATH].len;
            }
        }

        if(!host || *host == '-'){
            host = from_ip;
            host_len = strlen(from_ip);
        }
        
        if(!(port > 0)){
            port = 80;
        }
        
        if(!path){
            path = "/";
            path_len = 1;
        }
        
        gchar * cburl = g_strdup_printf ("http://%.*s:%d%.*s", host_len, host, port, path_len, path);
        rtc_endpoint_set_user_data(rtc, "cburl", cburl, strlen(cburl) + 1);
        rmgr_dbgi("cburl=%s", cburl);
        g_free(cburl);
        ret = 0;
        
    }while(0);
    
    return ret;
}

static
void on_http_cb_done(xhttp_str_t obj, void * cbcontext, int status_code, const char * response, int response_length, xhttp_str_msg_t msg){
    if(status_code != 200 || !response){
        rmgr_dbge("fail to http-cb [%s]", xhttp_str_msg_path(msg));
        return;
    }
    rmgr_dbgi("http-cb result: %.*s", response_length, response);
}

static inline
void call_rtc_http(rtc_manager_t obj, rtc_endpoint_t rtc, const char * evt_name){
    const char * cburl = (const char *) rtc_endpoint_get_user_data(rtc, "cburl");
    if(cburl){

        uint64_t audio_send_rtp = 0;
        uint64_t audio_send_rtcp = 0;
        uint64_t audio_send_dtls = 0;
        uint64_t audio_recv_rtp = 0;
        uint64_t audio_recv_rtcp = 0;
        uint64_t audio_recv_dtls = 0;
        uint64_t audio_recv_unknown = 0;
        uint64_t video_send_rtp = 0;
        uint64_t video_send_rtcp = 0;
        uint64_t video_send_dtls = 0;
        uint64_t video_recv_rtp = 0;
        uint64_t video_recv_rtcp = 0;
        uint64_t video_recv_dtls = 0;
        uint64_t video_recv_unknown = 0;

        rtc_endpoint_get_bytes(rtc, &audio_send_rtp, &audio_send_rtcp, &audio_send_dtls,
                               &audio_recv_rtp, &audio_recv_rtcp, &audio_recv_dtls, &audio_recv_unknown,
                               &video_send_rtp, &video_send_rtcp, &video_send_dtls,
                               &video_recv_rtp, &video_recv_rtcp, &video_recv_dtls, &video_recv_unknown);

        StringBuffer sb;
//        PrettyWriter<StringBuffer> writer(sb);
        Writer<StringBuffer> writer(sb);
        
        writer.StartObject();
        writer.Key("op");
        writer.String("rpccb");
        writer.Key("evt");
        writer.String(evt_name);
        writer.Key("rtcId");
        writer.String(rtc_endpoint_get_id(rtc));
        writer.Key("stats");
        writer.StartObject(); // stats {
        writer.Key("audio");
        writer.StartObject(); // stats.audio {
        writer.Key("send-rtp-bytes");
        writer.Uint64(audio_send_rtp);
        writer.Key("send-rtcp-bytes");
        writer.Uint64(audio_send_rtcp);
        writer.Key("recv-rtp-bytes");
        writer.Uint64(audio_recv_rtp);
        writer.Key("recv-rtcp-bytes");
        writer.Uint64(audio_recv_rtcp);
        writer.Key("recv-unknown-bytes");
        writer.Uint64(audio_recv_unknown);
        writer.EndObject();   // }
        writer.Key("video");
        writer.StartObject(); // stats.video {
        writer.Key("send-rtp-bytes");
        writer.Uint64(video_send_rtp);
        writer.Key("send-rtcp-bytes");
        writer.Uint64(video_send_rtcp);
        writer.Key("recv-rtp-bytes");
        writer.Uint64(video_recv_rtp);
        writer.Key("recv-rtcp-bytes");
        writer.Uint64(video_recv_rtcp);
        writer.Key("recv-unknown-bytes");
        writer.Uint64(video_recv_unknown);
        writer.EndObject();   // }
        writer.Key("dtls");
        writer.StartObject(); // stats.dtls {
        writer.Key("send-bytes");
        writer.Uint64(audio_send_dtls + video_send_dtls);
        writer.Key("recv-bytes");
        writer.Uint64(audio_recv_dtls + video_recv_dtls);
        writer.EndObject();   // }
        writer.EndObject();   //       }
        writer.EndObject();

        const char* cbstr = sb.GetString();
        rmgr_dbgi("posting callback to %s\n%s", cburl, cbstr);
        xhttp_str_post_json(obj->http_notifier, cburl, sb.GetString(), sb.GetSize(), on_http_cb_done, obj);
    }
}

static
void on_mgr_rtc_stream_event(rtc_endpoint_t rtc, void * context, int event, void * event_arg1, int event_arg2){
    rtc_manager_t obj = (rtc_manager_t) context;
    if(event == RTC_EVENT_SETUP){
        rmgr_dbgi("call http with rtc-setup [%s] ", rtc_endpoint_get_id(rtc));
        call_rtc_http(obj, rtc, "rtc-setup");
    }
}

static
void on_mgr_rtc_destroy(rtc_endpoint_t rtc, void * context){
    rmgr_dbgi("on_mgr_rtc_destroy");
    rtc_manager_t obj = (rtc_manager_t) context;
    call_rtc_http(obj, rtc, "rtc-free");
    std::string internal_source_name(rtc_endpoint_get_id(rtc));
    rtc_recorder_on_internal_source_closed(obj->rec, internal_source_name);
    push_to_talk_on_rtc_destroy(rtc);
    if (obj->pr != nullptr) {
        auto conf_id = std::string((char*)rtc_endpoint_get_user_data(rtc, "ctxId"));
        auto rtc_id = std::string(rtc_endpoint_get_id(rtc));
        proxy_del_keepalive_rtc(obj->pr, conf_id, rtc_id);
    }
    rmgr_dbgi("on_mgr_rtc_destroy end");
}


static
rtc_endpoint_callbacks g_mgr_rtc_listener = {
    .context = NULL, // context
    .on_destroy = on_mgr_rtc_destroy,
    .on_stream_data = NULL,
    .on_event = on_mgr_rtc_stream_event

};

#include <sys/stat.h>
#include <cstring>

static inline
int mk_dir_tree(const char * path){
    char tmp[4096] = {0};
    char *p = nullptr;
    snprintf(tmp, sizeof(tmp), "%s", path);
    size_t len = strlen(tmp);
    if (tmp[len - 1] == '/') {
        tmp[len - 1] = 0;
    }
    for (p = tmp + 1; *p; p++) {
        if(*p == '/') {
            *p = 0;
            mkdir(tmp, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
            *p = '/';
        }
    }
    mkdir(tmp, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
    return 0;
}


static
const char * rtcmgr_record_rtc(rtc_manager_t obj, Value &req, rtc_endpoint_t rtc){
    int ret = -1;
    const char * errmsg = NULL;
    
    do{
        const char * path = NULL;
        int path_len = 0;
        const char * fname = NULL;
        int fname_len = 0;
        

        Value::ConstMemberIterator it_fname = req.FindMember("fname");
        if(it_fname != req.MemberEnd() && it_fname->value.IsString()){
            fname = it_fname->value.GetString();
            fname_len = it_fname->value.GetStringLength();
            
        }else{
            break;
        }
        
        Value::ConstMemberIterator it_fpath = req.FindMember("path");
        if(it_fpath != req.MemberEnd() && it_fpath->value.IsString()){
            path = it_fpath->value.GetString();
            path_len = it_fpath->value.GetStringLength();
            
        }
        
        app_config * config = app_config_get();
        if(path){
            snprintf(obj->work_str, sizeof(obj->work_str), "%s/%.*s", config->tlv_path, path_len, path);
            ret = mk_dir_tree(obj->work_str);
            if(ret){
                rmgr_dbge("fail to mkdir [%s]", obj->work_str);
                errmsg = "set mkdir error";
                break;
            }
        }
        
        snprintf(obj->work_str, sizeof(obj->work_str), "%s/%.*s/%.*s.tlv", config->tlv_path, path_len, path, fname_len, fname);
        ret = rtc_endpoint_set_filename(rtc, obj->work_str, -1);
        if(ret){
            rmgr_dbge("fail to set fpath, rtc=[%s], fpath=[%s]", rtc_endpoint_get_id(rtc), obj->work_str);
            errmsg = "set fpath error";
            break;
        }
        rmgr_dbgi("set fpath, rtc=[%s], fpath=[%s]", rtc_endpoint_get_id(rtc), obj->work_str);
        ret = 0;
    }while(0);
    return errmsg;
}


static
int rtcmgr_handle_initC(rtc_manager_t obj, Value &req, const uint8_t * json, int json_len, const char * from_ip, int from_port, Writer<StringBuffer> &writer){
    int ret = -1;
    const char * rtc_id = NULL;
    rtc_endpoint_t rtc = NULL;
    const char * errmsg = NULL;
    do{
        Value::ConstMemberIterator itRtcId = req.FindMember("rtcId");
        if (itRtcId == req.MemberEnd()) {
            rmgr_dbge("initC did not provide rtcId");
            errmsg = "no rtcId";
            ret = 400;
            break;
        } else if (!itRtcId->value.IsString()) {
            rmgr_dbge("initC.rtcId not string");
            errmsg = "rtcId not string";
            ret = 400;
            break;
        }

        bool dropAudio = false;
        bool dropVideo = false;

        Value::ConstMemberIterator itDropAudio = req.FindMember("drop_audio");
        Value::ConstMemberIterator itDropVideo = req.FindMember("drop_video");
        if (itDropAudio != req.MemberEnd() && itDropAudio->value.IsBool()) {
            dropAudio = itDropAudio->value.GetBool();
            if (dropAudio) {
                rmgr_dbgi("initC instructed to drop audio");
            }
        }
        if (itDropVideo != req.MemberEnd() && itDropVideo->value.IsBool()) {
            dropVideo = itDropVideo->value.GetBool();
            if (dropVideo) {
                rmgr_dbgi("initC instructed to drop video");
            }
        }

        std::string rtcId(itRtcId->value.GetString(), itRtcId->value.GetStringLength());

        app_config * config = app_config_get();

        int max_video_bitrate_kbps = config->max_video_bitrate_kbps;
        int min_video_bitrate_kbps = config->min_video_bitrate_kbps;

        Value::ConstMemberIterator itMaxVideoBitrate = req.FindMember("maxVideoBitrate");
        if (itMaxVideoBitrate != req.MemberEnd() && itMaxVideoBitrate->value.IsInt()) {
            max_video_bitrate_kbps = itMaxVideoBitrate->value.GetInt();
        }

        Value::ConstMemberIterator itMinVideoBitrate = req.FindMember("minVideoBitrate");
        if (itMinVideoBitrate != req.MemberEnd() && itMinVideoBitrate->value.IsInt()) {
            min_video_bitrate_kbps = itMinVideoBitrate->value.GetInt();
        }

        rtc = rtc_endpoint_create(obj->main_context, rtcId, config->media_ip, config->min_port, config->max_port,
                                  config->is_dumping_rtp, config->is_dumping_rtcp,
                                  max_video_bitrate_kbps,
                                  min_video_bitrate_kbps,
                                  config->is_translating_fec,
                                  config->disable_h264,
                                  config->enable_fec,
                                  config->compensate_rtcp,
                                  config->experiment_esmb,
                                  config->lost_0,
                                  config->enable_bwe,
                                  false,
                                  true,
                                  32,
                                  dropAudio,
                                  dropVideo
        );
        if(!rtc){
            rmgr_dbge("fail to create answer rtc");
            errmsg = "fail to create answer rtc";
            ret = -102;
            break;
        }
        rtc_id = rtc_endpoint_get_id(rtc);
        rmgr_dbgi("rtc_id=%s, max_video_bitrate_kbps=%d, min_video_bitrate_kbps=%d", rtc_id, max_video_bitrate_kbps, min_video_bitrate_kbps);
        (*obj->rtc_map)[rtc_id] = rtc;
        rtc_endpoint_register_callback(rtc, obj, &g_mgr_rtc_listener);

        errmsg = rtcmgr_record_rtc(obj, req, rtc);
        if(errmsg){
            ret = -103;
            break;
        }
        
        
        Value::ConstMemberIterator it_cburl = req.FindMember("cburl");
        if(it_cburl != req.MemberEnd() && it_cburl->value.IsString()){
            ret = rtcmgr_set_rtc_cburl(obj, rtc, it_cburl->value.GetString(), it_cburl->value.GetStringLength(), from_ip, from_port);
            if(ret){
                errmsg = "cburl error";
                ret = -105;
                break;
            }
        }

        std::string ctx_id;
        Value::ConstMemberIterator it_ctxid = req.FindMember("ctxId");
        if (it_ctxid != req.MemberEnd() && it_ctxid->value.IsString()) {
            ctx_id = std::string(it_ctxid->value.GetString(), it_ctxid->value.GetStringLength());
            rtc_endpoint_set_user_data(rtc, "ctxId", (const void*)it_ctxid->value.GetString(), it_ctxid->value.GetStringLength() + 1);
        } else {
            auto unknown = "unknown";
            rtc_endpoint_set_user_data(rtc, "ctxId", unknown, strlen(unknown) + 1);
        }

        const char * sdp_type;
        Value::ConstMemberIterator it_sdp = req.FindMember("sdp");
        Value::ConstMemberIterator it_vcodecs = req.FindMember("vcodecs");
        if(it_sdp == req.MemberEnd() || (!it_sdp->value.IsObject() && !it_sdp->value.IsString())){
            std::list<std::string> vcodecs;
            if (it_vcodecs != req.MemberEnd() && it_vcodecs->value.IsArray()) {
                auto vcodec_array = it_vcodecs->value.GetArray();
                for (auto& vcodec : vcodec_array) {
                    if (!vcodec.IsString()) {
                        continue;
                    }
                    vcodecs.emplace_back(std::string(vcodec.GetString(), vcodec.GetStringLength()));
                }
            }
            ret = rtc_endpoint_generate_offer(rtc, vcodecs);
            if(ret){
                rmgr_dbge("generate offer error %d", ret);
                errmsg = "generate offer error";
                ret = -110;
                break;
            }
            
            sdp_type = "offer";
            rmgr_dbgi("generate offer OK, [%s]", rtc_endpoint_get_id(rtc));
            
            // end of generate offer
        }else{
            
            const Value *psdp_value ;
            Document sdp_doc;
            if(it_sdp->value.IsString()){
                sdp_doc.Parse(it_sdp->value.GetString(), it_sdp->value.GetStringLength());
                if(!sdp_doc.IsObject()){
                    rmgr_dbge("generate sdp json error");
                    errmsg = "generate sdp json error";
                    ret = -112;
                    break;
                }
                psdp_value = &sdp_doc;
            }else{
                psdp_value = &it_sdp->value;
            }
            const Value &sdp_value = *psdp_value;
            
            Value::ConstMemberIterator it_sdptype = sdp_value.FindMember("type");
            if(it_sdptype == sdp_value.MemberEnd() || !it_sdptype->value.IsString()){
                rmgr_dbge("get field sdp.type error %d, json: %.*s", ret, json_len, json);
                errmsg = "error field sdp.type";
                ret = -115;
                break;
            }
            std::string sdptype(it_sdptype->value.GetString(), it_sdptype->value.GetStringLength());
            
            Value::ConstMemberIterator it_sdpdesc = sdp_value.FindMember("sdp");
            if(it_sdpdesc == sdp_value.MemberEnd() || !it_sdpdesc->value.IsString()){
                rmgr_dbge("get field sdp.sdp error %d, json: %.*s", ret, json_len, json);
                errmsg = "error field sdp.sdp";
                ret = -120;
                break;
            }
            
            ret = rtc_endpoint_process_offer(rtc, it_sdpdesc->value.GetString(), it_sdpdesc->value.GetStringLength());
            if(ret){
                rmgr_dbge("rtc_endpoint_process_offer fail, %d", ret);
                errmsg = "error offer";
                ret = -125;
                break;
            }
            
            ret = rtcmgr_maybe_set_candidates(obj, rtc, req);
            if(ret) {
                errmsg = "error candidates";
                ret = -130;
                break;
            }
            
            
            int is_pranswer = 0;
            ret = rtc_endpoint_generate_answer(rtc, is_pranswer);
            if(ret){
                rmgr_dbge("rtc_endpoint_generate_answer fail, %d", ret);
                errmsg = "generate answer fail";
                ret = -135;
                break;
            }
            
            sdp_type = "answer";
            rmgr_dbgi("generate answer OK, [%s]", rtc_endpoint_get_id(rtc));
            // end of generate answer
        }

        std::string conf_type;
        Value::ConstMemberIterator it_conf_type = req.FindMember("confType");
        if (it_conf_type != req.MemberEnd() && it_conf_type->value.IsString()) {
            conf_type = std::string(it_conf_type->value.GetString(), it_conf_type->value.GetStringLength());
        }

        std::string ctx;
        Value::ConstMemberIterator it_ctx = req.FindMember("ctx");
        if (it_ctx != req.MemberEnd() && it_ctx->value.IsString()) {
            ctx = std::string(it_ctx->value.GetString(), it_ctx->value.GetStringLength());
        }

        bool recEnabled = false;
        std::string recOutDir;

        Value::ConstMemberIterator it_reccfg = req.FindMember("recCfg");
        if (it_reccfg != req.MemberEnd() && it_reccfg->value.IsObject()) {
            Value::ConstObject recCfg = it_reccfg->value.GetObject();
            Value::ConstMemberIterator it_reccfg_enabled = recCfg.FindMember("enabled");
            if (it_reccfg_enabled != recCfg.MemberEnd() && it_reccfg_enabled->value.IsBool()) {
                recEnabled = it_reccfg_enabled->value.GetBool();
            }
            Value::ConstMemberIterator it_reccfg_outdir = recCfg.FindMember("outDir");
            if (it_reccfg_outdir != recCfg.MemberEnd() && it_reccfg_outdir->value.IsString()) {
                recOutDir = std::string(it_reccfg_outdir->value.GetString(), it_reccfg_outdir->value.GetStringLength());
            }
        }


        Value::ConstMemberIterator it_csrc = req.FindMember("csrc");
        if (it_csrc != req.MemberEnd() && it_csrc->value.IsInt()) {
            rtc_endpoint_override_remote_audio_ssrc(rtc, it_csrc->value.GetInt());
        }

        if (conf_type == "push-to-talk") {
            if (ctx_id.empty()) {
                rmgr_dbge("unable to connect to push-to-talk conference, ctx_id is empty");
            } else {
                push_to_talk_join(ctx_id, rtc);
            }
        } else if (conf_type == "audio-mixer") {
            if (config->force_mixer_recording) {
                recEnabled = true;
            }
            bool is_mixer_new;
            std::string link_addr;

            int err = mixer_manager_add_rtc(obj->mixer_mgr, rtc, ctx_id, recEnabled, recOutDir,
                                            ctx, is_mixer_new, link_addr);
            if (err != 0) {
                writer.Key("error");
                writer.String(mixer_manager_strerr(err));
                ret = -138;
                break;
            }
            writer.Key("link-audio");
            writer.Bool(true);
            writer.Key("link-update");
            writer.Bool(is_mixer_new);
            writer.Key("link-addr");
            writer.String(link_addr);
        }

        std::vector<uint32_t> relay_audio_ssrcs;
        std::vector<uint32_t> relay_video_ssrcs;
        rtc_endpoint_get_relay_audio_ssrc_list(rtc, relay_audio_ssrcs);
        rtc_endpoint_get_relay_video_ssrc_list(rtc, relay_video_ssrcs);

        // connect rtc after mixer, so that mixer will terminate incoming audio
        // otherwise, audio was send twice by mixer and connector
        if (config->debug_mode_string) {
            xplugin_rtc_debug_rtc(obj->main_context, rtc, config->debug_mode_string);
        }

        int local_sdp_len = 0;
        const char * local_sdp = rtc_endpoint_get_local_sdp(rtc, &local_sdp_len);
        
        writer.Key("rtcId");
        writer.String(rtc_endpoint_get_id(rtc));
        
        writer.Key("sdp");
        writer.StartObject();
        writer.Key("sdp");
        writer.String(local_sdp, local_sdp_len);
        writer.Key("type");
        writer.String(sdp_type);
        writer.Key("seq");
        writer.Int(1);
        writer.EndObject();
        
        
        writer.Key("candidates");
        writer.StartArray();
        int mlines = rtc_endpoint_get_mline_count(rtc);
        for(int mlineindex = 0; mlineindex < mlines; mlineindex++){
            
            for(int stage = 0; stage <= 1; stage++){
                const char * mid = NULL;
                int candidate_count = 0;
                char **candidates = NULL;
                ret = rtc_endpoint_get_candidates(rtc, stage, mlineindex, &mid, &candidate_count, &candidates);
                if(ret){
                    rmgr_dbge("rtc_endpoint_get_canidates fail, %d, stage=%d, mlineindex=%d", ret, stage, mlineindex);
                    errmsg = "get candidates fail";
                    ret = -140;
                    break;
                }
                
                for(int ci = 0; ci < candidate_count; ci++){
                    
                    //  candidate sdp,     see rfc5245#section-15
                    gchar * cand_sdp = candidates[ci];
                    //rmgr_dbgi(" local candidate[%d] = %s", ci, cand_sdp);
                    
                    writer.StartObject();
                    writer.Key("candidate");
                    writer.String(cand_sdp+2);
                    
                    writer.Key("mid");
                    writer.String(mid);
                    writer.Key("sdpMid");
                    writer.String(mid);
                    
                    writer.Key("mlineindex");
                    writer.Int(mlineindex);
                    writer.Key("sdpMLineIndex");
                    writer.Int(mlineindex);
                    
                    
                    writer.Key("type");
                    writer.String("candidate");
                    writer.Key("seq");
                    writer.Int(1);
                    writer.EndObject();
                }
            }
            
            
        }
        if(ret) break;

        writer.EndArray();


        //if (strcmp(sdp_type, "answer") == 0) {
        {
            writer.Key("relayAudioSSRCs");
            writer.StartArray();
            for (auto &ssrc : relay_audio_ssrcs) {
                writer.Uint(ssrc);
            }
            if (relay_audio_ssrcs.size() % 2 != 0) {
                writer.Uint(0);
            }
            writer.EndArray();

            writer.Key("relayVideoSSRCs");
            writer.StartArray();
            for (auto &ssrc : relay_video_ssrcs) {
                writer.Uint(ssrc);
            }
            if (relay_video_ssrcs.size() % 2 != 0) {
                writer.Uint(0);
            }
            writer.EndArray();
        }

//        writer.EndObject();
//        memcpy(rsp_buf, sb.GetString(),  sb.GetSize());
//        *rsp_length = sb.GetSize();

        if (obj->pr != nullptr) {
            proxy_add_keepalive_rtc(obj->pr, ctx_id, rtc_id);
        }

        ret = 0;
    }while(0);

    if(ret){
        if(rtc_id){
            rtcmgr_remove_rtc(obj, rtc_id);
        }
    }

    if(errmsg){
        writer.Key("errmsg");
        writer.String(errmsg);
    }
    
    return ret;
}

static
int rtcmgr_handle_ansC(rtc_manager_t obj, Value &req, const uint8_t * json, int json_len, Writer<StringBuffer> &writer){
    int ret = -1;
    const char * errmsg = NULL;
    do{
        
        Value::ConstMemberIterator it_rtcid = req.FindMember("rtcId");
        if(it_rtcid == req.MemberEnd() || !it_rtcid->value.IsString()){
            rmgr_dbge("get field rtcId error %d, json: %.*s", ret, json_len, json);
            errmsg = "error field rtcId";
            ret = -205;
            break;
        }
        
        const char * rtc_id = it_rtcid->value.GetString();
        std::map<std::string, rtc_endpoint_t>::iterator it = obj->rtc_map->find(rtc_id);
        if (it == obj->rtc_map->end()){
            rmgr_dbge("NOT found rtcId %s", rtc_id);
            errmsg = "non-exist rtc";
            ret = -210;
            break;
        }
        
        rtc_endpoint_t rtc = it->second;
        
        Value::ConstMemberIterator it_sdp = req.FindMember("sdp");
        if(it_sdp == req.MemberEnd() || (!it_sdp->value.IsObject() && !it_sdp->value.IsString())){
            rmgr_dbge("get field sdp error %d, json: %.*s", ret, json_len, json);
            errmsg = "error field sdp";
            ret = -215;
            break;
        }
        
        const Value *psdp_value ;
        Document sdp_doc;
        if(it_sdp->value.IsString()){
            sdp_doc.Parse(it_sdp->value.GetString(), it_sdp->value.GetStringLength());
            if(!sdp_doc.IsObject()){
                rmgr_dbge("generate sdp json error");
                errmsg = "generate sdp json error";
                ret = -112;
                break;
            }
            psdp_value = &sdp_doc;
        }else{
            psdp_value = &it_sdp->value;
        }
        const Value &sdp_value = *psdp_value;
        
        Value::ConstMemberIterator it_sdptype = sdp_value.FindMember("type");
        if(it_sdptype == sdp_value.MemberEnd() || !it_sdptype->value.IsString()){
            rmgr_dbge("get field sdp.type error %d, json: %.*s", ret, json_len, json);
            errmsg = "error field sdp.type";
            ret = -220;
            break;
        }
        std::string sdptype(it_sdptype->value.GetString(), it_sdptype->value.GetStringLength());
        int is_pranswer = 0;
        if(sdptype == "pranswer" || sdptype == "PRANSWER"){
            is_pranswer = 1;
        }else if(sdptype == "answer" || sdptype == "ANSWER"){
            is_pranswer = 0;
        }else{
            rmgr_dbge("wrong field sdp.type, json: %.*s", json_len, json);
            errmsg = "wrong field sdp.type";
            ret = -222;
            break;
        }
        
        Value::ConstMemberIterator it_sdpdesc = sdp_value.FindMember("sdp");
        if(it_sdpdesc == sdp_value.MemberEnd() || !it_sdpdesc->value.IsString()){
            rmgr_dbge("get field sdp.sdp error %d, json: %.*s", ret, json_len, json);
            errmsg = "error field sdp.sdp";
            ret = -225;
            break;
        }
        
        ret = rtc_endpoint_process_answer(rtc, it_sdpdesc->value.GetString(), it_sdpdesc->value.GetStringLength(), is_pranswer);
        if(ret){
            rmgr_dbge("rtc_endpoint_process_answer fail, %d", ret);
            errmsg = "process answer fail";
            ret = -230;
            break;
        }
        
        ret = rtcmgr_maybe_set_candidates(obj, rtc, req);
        if(ret) {
            errmsg = "error candidates";
            ret = -235;
            break;
        }
        
        
//        StringBuffer sb;
//        PrettyWriter<StringBuffer> writer(sb);
//        writer.StartObject();
//        writer.Key("op");
//        writer.String("ok");
        
        writer.Key("rtcId");
        writer.String(rtc_endpoint_get_id(rtc));

        std::vector<uint32_t> relay_audio_ssrcs;
        std::vector<uint32_t> relay_video_ssrcs;

        rtc_endpoint_get_relay_audio_ssrc_list(rtc, relay_audio_ssrcs);
        rtc_endpoint_get_relay_video_ssrc_list(rtc, relay_video_ssrcs);

        writer.Key("relayAudioSSRCs");
        writer.StartArray();
        for (auto &ssrc : relay_audio_ssrcs) {
            writer.Uint(ssrc);
        }
        if (relay_audio_ssrcs.size() % 2 != 0) {
            writer.Uint(0);
        }
        writer.EndArray();

        writer.Key("relayVideoSSRCs");
        writer.StartArray();
        for (auto &ssrc : relay_video_ssrcs) {
            writer.Uint(ssrc);
        }
        if (relay_video_ssrcs.size() % 2 != 0) {
            writer.Uint(0);
        }
        writer.EndArray();

//        writer.EndObject();
//        memcpy(rsp_buf, sb.GetString(),  sb.GetSize());
//        *rsp_length = sb.GetSize();
        
        
        ret = 0;
    }while(0);
    
    if(errmsg){
        writer.Key("errmsg");
        writer.String(errmsg);
    }
    
    return ret;
}


static
int rtcmgr_handle_termC(rtc_manager_t obj, Value &req, const uint8_t * json, int json_len, Writer<StringBuffer> &writer){
    int ret = -1;
    const char * errmsg = NULL;
    do{
        Value::ConstMemberIterator it_rtcid = req.FindMember("rtcId");
        if(it_rtcid == req.MemberEnd() || !it_rtcid->value.IsString()){
            rmgr_dbge("get field rtcId error %d, json: %.*s", ret, json_len, json);
            errmsg = "error field rtcId";
            ret = -305;
            break;
        }
        
        const char * rtc_id = it_rtcid->value.GetString();
        ret = rtcmgr_remove_rtc(obj, rtc_id);
        if(ret) {
            errmsg = "non-exist rtc";
            ret = -310;
            break;
        }
        
//        StringBuffer sb;
//        PrettyWriter<StringBuffer> writer(sb);
//        writer.StartObject();
//        writer.Key("op");
//        writer.String("ok");
        
        writer.Key("rtcId");
        writer.String(rtc_id);
        
//        writer.EndObject();
//        memcpy(rsp_buf, sb.GetString(),  sb.GetSize());
//        *rsp_length = sb.GetSize();
        
        ret = 0;
    }while(0);
    
    if(errmsg){
        writer.Key("errmsg");
        writer.String(errmsg);
    }
    
    return ret;
}

static
int rtcmgr_handle_rtc_upd(rtc_manager_t obj, Value &req, const uint8_t * json, int json_len, Writer<StringBuffer> &writer){
    int ret = -1;
    const char * errmsg = NULL;
    do{
        Value::ConstMemberIterator it_rtcid = req.FindMember("rtcId");
        if(it_rtcid == req.MemberEnd() || !it_rtcid->value.IsString()){
            rmgr_dbge("get field rtcId error %d, json: %.*s", ret, json_len, json);
            errmsg = "error field rtcId";
            ret = -305;
            break;
        }

        const char * rtc_id = it_rtcid->value.GetString();
        rtc_endpoint_t rtc = rtcmgr_get_rtc(obj, rtc_id, 0);
        if(!rtc){
            rmgr_dbge("NOT found rtcId %s", rtc_id);
            ret = -310;
            break;
        }

        bool dropAudio = false;
        bool dropVideo = false;
        rtc_endpoint_get_drop(rtc, dropAudio, dropVideo);

        Value::ConstMemberIterator itDropAudio = req.FindMember("drop_audio");
        Value::ConstMemberIterator itDropVideo = req.FindMember("drop_video");
        if (itDropAudio != req.MemberEnd() && itDropAudio->value.IsBool()) {
            dropAudio = itDropAudio->value.GetBool();
            if (dropAudio) {
                rmgr_dbgi("[%s] rtcupd instructed to drop audio", rtc_id);
            }else{
                rmgr_dbgi("[%s] rtcupd instructed to open audio", rtc_id);
            }
        }
        if (itDropVideo != req.MemberEnd() && itDropVideo->value.IsBool()) {
            dropVideo = itDropVideo->value.GetBool();
            if (dropVideo) {
                rmgr_dbgi("[%s] rtcupd instructed to drop video", rtc_id);
            } else {
                rmgr_dbgi("[%s] rtcupd instructed to open video", rtc_id);
            }
        }
        rtc_endpoint_set_drop(rtc, dropAudio, dropVideo);

        writer.Key("rtcId");
        writer.String(rtc_id);

        writer.Key("drop_audio");
        writer.Bool(dropAudio);

        writer.Key("drop_video");
        writer.Bool(dropVideo);

        ret = 0;
    }while(0);

    if(errmsg){
        writer.Key("errmsg");
        writer.String(errmsg);
    }

    return ret;
}

static
int rtcmgr_handle_tcklC(rtc_manager_t obj, Value &req, const uint8_t * json, int json_len, Writer<StringBuffer> &writer){
    int ret = -1;
    const char * errmsg = NULL;
    do{
        Value::ConstMemberIterator it_rtcid = req.FindMember("rtcId");
        if(it_rtcid == req.MemberEnd() || !it_rtcid->value.IsString()){
            rmgr_dbge("get field rtcId error %d, json: %.*s", ret, json_len, json);
            errmsg = "error field rtcId";
            ret = -405;
            break;
        }
        
        const char * rtc_id = it_rtcid->value.GetString();
        std::map<std::string, rtc_endpoint_t>::iterator it = obj->rtc_map->find(rtc_id);
        if (it == obj->rtc_map->end()){
            rmgr_dbge("NOT found rtcId %s", rtc_id);
            errmsg = "non-exist rtc";
            ret = -410;
            break;
        }
        
        rtc_endpoint_t rtc = it->second;
        
        
        
        ret = rtcmgr_maybe_set_candidates(obj, rtc, req);
        if(ret) break;
        
        
//        StringBuffer sb;
//        PrettyWriter<StringBuffer> writer(sb);
//        writer.StartObject();
//        writer.Key("op");
//        writer.String("ok");
        
        writer.Key("rtcId");
        writer.String(rtc_id);
        
//        writer.EndObject();
//        memcpy(rsp_buf, sb.GetString(),  sb.GetSize());
//        *rsp_length = sb.GetSize();
        
        
        ret = 0;
    }while(0);
    
    if(errmsg){
        writer.Key("errmsg");
        writer.String(errmsg);
    }
    
    return ret;
}

static
int rtcmgr_handle_subC(rtc_manager_t obj, Value &req, const uint8_t * json, int json_len,std::string& jsondata,Writer<StringBuffer> &writer){
    int ret = -1;
    const char * errmsg = NULL;
    do{
        Value::ConstMemberIterator it_rtcid = req.FindMember("rtcId");
        if(it_rtcid == req.MemberEnd() || !it_rtcid->value.IsString()){
            rmgr_dbge("get field rtcId error %d, json: %.*s", ret, json_len, json);
            errmsg = "error field rtcId";
            ret = -505;
            break;
        }
        
        const char * rtc_id = it_rtcid->value.GetString();
        std::map<std::string, rtc_endpoint_t>::iterator it = obj->rtc_map->find(rtc_id);
        if (it == obj->rtc_map->end()){
            rmgr_dbge("NOT found rtcId %.*s", it_rtcid->value.GetStringLength(), rtc_id);
            errmsg = "non-exist rtc";
            ret = -510;
            break;
        }
        

        Value::ConstMemberIterator it_pubrtcid = req.FindMember("pubRtcId");
        if(it_pubrtcid == req.MemberEnd() || !it_pubrtcid->value.IsString()){
            rmgr_dbge("get field subRtcId error %d, json: %.*s", ret, json_len, json);
            errmsg = "error field pubRtcId";
            ret = -515;
            break;
        }
        
        const char * pub_rtc_id = it_pubrtcid->value.GetString();
        std::map<std::string, rtc_endpoint_t>::iterator it_pubrtc = obj->rtc_map->find(pub_rtc_id);
        if (it_pubrtc == obj->rtc_map->end()){
            rmgr_dbge("NOT found pubRtcId %.*s", it_pubrtcid->value.GetStringLength(), pub_rtc_id);
            errmsg = "non-exist pubRtc";
            ret = -520;
            break;
        }

        bool audio = false;
        Value::ConstMemberIterator it_audio = req.FindMember("audio");

        if (it_audio != req.MemberEnd() && it_audio->value.IsBool()) {
            audio = it_audio->value.GetBool();
        }
        bool video = false;
        Value::ConstMemberIterator it_video = req.FindMember("video");
        if (it_video != req.MemberEnd() && it_video->value.IsBool()) {
            video = it_video->value.GetBool();
        }


//        if (it_audio == req.MemberEnd() && it_video == req.MemberEnd()) {
//            audio = true;
//            video = true;
//        }

//        std::vector<uint32_t> peer_audio_ssrcs;
//        std::vector<uint32_t> peer_video_ssrcs;
//        {
//            Value::ConstMemberIterator it_audio = req.FindMember("peerAudioSSRCs");
//            if (it_audio != req.MemberEnd() && it_audio->value.IsArray()) {
//                for (auto &it : it_audio->value.GetArray()) {
//                    if (!it.IsUint()) {
//                        rmgr_dbge("handle_subC: peerAudioSSRCs contains invalid ssrc which is ignored");
//                        continue;
//                    }
//                    peer_audio_ssrcs.push_back(it.GetUint());
//                }
//            } else {
//                rmgr_dbge("handle_subC: could not read field 'peerAudioSSRCs', default to no audio");
//            }
//
//            Value::ConstMemberIterator it_video = req.FindMember("peerVideoSSRCs");
//            if (it_video != req.MemberEnd() && it_video->value.IsArray()) {
//                for (auto &it : it_video->value.GetArray()) {
//                    if (!it.IsUint()) {
//                        rmgr_dbge("handle_subC: peerVideoSSRCs contains invalid ssrc which is ignored");
//                        continue;
//                    }
//                    peer_video_ssrcs.push_back(it.GetUint());
//                }
//            } else {
//                rmgr_dbge("handle_subC: could not read field 'peerVideoSSRCs', default to no videos");
//            }
//        }

        rtc_endpoint_t rtc = it->second;
        rtc_endpoint_t pubrtc = it_pubrtc->second;

        ret = rtc_endpoint_subscribe(rtc, pubrtc, audio, video);
        if(ret){
            rmgr_dbge("subscribe fail, pub_rtc_id=[%.*s]", it_pubrtcid->value.GetStringLength(), pub_rtc_id);
            errmsg = "subscribe fail";
            ret = -525;
            break;
        }
        
#ifdef AUTO_RECORD
        // TODO: remove these code
        if(!rtc_endpoint_is_offer_role(pubrtc)){
            app_config * config = app_config_get();
            auto_record_rtc(pubrtc, config->record_path);
        }
#endif

        
//        writer.Key("rtcId");
//        writer.String(rtc_id);
        
        
        ret = 0;
    }while(0);
    
    if(errmsg){
        writer.Key("errmsg");
        writer.String(errmsg);
    }
    
    return ret;
}

static
int rtcmgr_handle_usubC(rtc_manager_t obj, Value &req, const uint8_t * json, int json_len, Writer<StringBuffer> &writer){
    int ret = -1;
    const char * errmsg = NULL;
    do{
        Value::ConstMemberIterator it_rtcid = req.FindMember("rtcId");
        if(it_rtcid == req.MemberEnd() || !it_rtcid->value.IsString()){
            rmgr_dbge("get field rtcId error %d, json: %.*s", ret, json_len, json);
            errmsg = "error field rtcId";
            ret = -555;
            break;
        }
        
        const char * rtc_id = it_rtcid->value.GetString();
        std::map<std::string, rtc_endpoint_t>::iterator it = obj->rtc_map->find(rtc_id);
        if (it == obj->rtc_map->end()){
            rmgr_dbge("NOT found rtcId %.*s", it_rtcid->value.GetStringLength(), rtc_id);
            errmsg = "non-exist rtc";
            ret = -560;
            break;
        }
        
        
        Value::ConstMemberIterator it_pubrtcid = req.FindMember("pubRtcId");
        if(it_pubrtcid == req.MemberEnd() || !it_pubrtcid->value.IsString()){
            rmgr_dbge("get field subRtcId error %d, json: %.*s", ret, json_len, json);
            errmsg = "error field pubRtcId";
            ret = -565;
            break;
        }
        
        const char * pub_rtc_id = it_pubrtcid->value.GetString();
        std::map<std::string, rtc_endpoint_t>::iterator it_pubrtc = obj->rtc_map->find(pub_rtc_id);
        if (it_pubrtc == obj->rtc_map->end()){
            rmgr_dbge("NOT found pubRtcId %.*s", it_pubrtcid->value.GetStringLength(), pub_rtc_id);
            errmsg = "non-exist pubRtc";
            ret = -570;
            break;
        }
        
        rtc_endpoint_t rtc = it->second;
        rtc_endpoint_t pubrtc = it_pubrtc->second;
        ret = rtc_endpoint_unsubscribe(rtc, pubrtc);
        if(ret){
            rmgr_dbge("rtc_endpoint_unsubscribe fail, pub_rtc_id=[%.*s]", it_pubrtcid->value.GetStringLength(), pub_rtc_id);
            errmsg = "unsubscribe fail";
            ret = -575;
            break;
        }
        
        ret = 0;
    }while(0);
    
    if(errmsg){
        writer.Key("errmsg");
        writer.String(errmsg);
    }
    
    return ret;
}

static int rtcmgr_handle_sub_remote(rtc_manager_t obj, Value &req, const uint8_t* json, int json_len, Writer<StringBuffer>& writer) {
    std::string sub_rtc_id;
    std::string pub_rtc_id;

    Value::ConstMemberIterator it_subrtcid = req.FindMember("subRtcId");
    if (it_subrtcid != req.MemberEnd() && it_subrtcid->value.IsString()) {
        sub_rtc_id = std::string(it_subrtcid->value.GetString(), it_subrtcid->value.GetStringLength());
    } else {
        rmgr_dbge("handle_sub_remote: could not read field 'subRtcId'");
        writer.Key("errmsg");
        writer.String("could not read field 'subRtcId'");
        return 400;
    }

    Value::ConstMemberIterator it_pubrtcid = req.FindMember("pubRtcId");
    if (it_pubrtcid != req.MemberEnd() && it_pubrtcid->value.IsString()) {
        pub_rtc_id = std::string(it_pubrtcid->value.GetString(), it_pubrtcid->value.GetStringLength());
    } else {
        rmgr_dbge("handle_sub_remote: could not read field 'pubRtcId'");
        writer.Key("errmsg");
        writer.String("could not read field 'pubRtcId'");
        return 400;
    }

    std::vector<uint32_t> peer_audio_ssrcs; // pub rtc xswitch local ssrc
    std::vector<uint32_t> peer_video_ssrcs; // pub rtc xswitch local ssrc

    Value::ConstMemberIterator it_audio = req.FindMember("peerAudioSSRCs");
    if (it_audio != req.MemberEnd() && it_audio->value.IsArray()) {
        for (auto &it : it_audio->value.GetArray()) {
            if (!it.IsUint()) {
                rmgr_dbge("handle_sub_remote: peerAudioSSRCs contains invalid ssrc which is ignored");
                continue;
            }
            peer_audio_ssrcs.push_back(it.GetUint());
        }
    } else {
        rmgr_dbge("handle_sub_remote: could not read field 'peerAudioSSRCs', default to no audio");
    }

    Value::ConstMemberIterator it_video = req.FindMember("peerVideoSSRCs");
    if (it_video != req.MemberEnd() && it_video->value.IsArray()) {
        for (auto &it : it_video->value.GetArray()) {
            if (!it.IsUint()) {
                rmgr_dbge("handle_sub_remote: peerVideoSSRCs contains invalid ssrc which is ignored");
                continue;
            }
            peer_video_ssrcs.push_back(it.GetUint());
        }
    } else {
        rmgr_dbge("handle_sub_remote: could not read field 'peerVideoSSRCs', default to no videos");
    }

    auto rtc_it = obj->rtc_map->find(sub_rtc_id);
    if (rtc_it == obj->rtc_map->end()) {
        rmgr_dbge("handle_sub_remote: could not find sub rtc '%s'", sub_rtc_id.c_str());
        writer.Key("errmsg");
        writer.String("could not find sub rtc");
        return 404;
    }

    auto rtc = rtc_it->second;

    auto status = rcm_add_linker(obj->rcm, pub_rtc_id.c_str(), rtc, peer_audio_ssrcs,
                                 peer_video_ssrcs);
    if (status.audio) {
        writer.Key("link-audio");
        writer.Bool(true);
    }
    if (status.video) {
        writer.Key("link-video");
        writer.Bool(true);
    }
    if (status.update) {
        writer.Key("link-update");
        writer.Bool(true);
    }

    {
        std::vector<uint32_t> audio_ssrcs;
        std::vector<uint32_t> video_ssrcs;

        rtc_endpoint_get_local_audio_ssrc(rtc, audio_ssrcs);
        rtc_endpoint_get_local_video_ssrc(rtc, video_ssrcs);

        writer.Key("audioSSRCs");
        writer.StartArray();
        for (auto &ssrc : audio_ssrcs) {
            writer.Uint(ssrc);
        }
        if (audio_ssrcs.size() % 2 != 0) {
            writer.Uint(0);
        }
        writer.EndArray();

        writer.Key("videoSSRCs");
        writer.StartArray();
        for (auto &ssrc : video_ssrcs) {
            writer.Uint(ssrc);
        }
        if (video_ssrcs.size() % 2 != 0) {
            writer.Uint(0);
        }
        writer.EndArray();
    }

    auto link_addr = rcm_get_address(obj->rcm, pub_rtc_id.c_str());
    if (link_addr != nullptr) {
        writer.Key("link-addr");
        writer.String(link_addr);
    }

    rmgr_dbgi("handle_sub_remote ok: status: %d, remote pub rtc: '%s', local sub rtc: '%s', link-update: %s, link-audio: %s, link-video: %s, link-addr: '%s'",
              status.status,
              pub_rtc_id.c_str(),
              sub_rtc_id.c_str(),
              status.update ? "yes" : "no",
              status.audio ? "yes" : "no",
              status.video ? "yes" : "no",
              link_addr);
    return status.status;
}

static int rtcmgr_handle_usub_remote(rtc_manager_t obj, Value &req, const uint8_t* json, int json_len, Writer<StringBuffer>& writer) {
    std::string pub_rtc_id;
    std::string sub_rtc_id;

    Value::ConstMemberIterator it_subrtcid = req.FindMember("subRtcId");
    if (it_subrtcid != req.MemberEnd() && it_subrtcid->value.IsString()) {
        sub_rtc_id = std::string(it_subrtcid->value.GetString(), it_subrtcid->value.GetStringLength());
    } else {
        rmgr_dbge("handle_usub_remote: could not read field 'subRtcId'");
        writer.Key("errmsg");
        writer.String("could not read field 'subRtcId'");
        return 400;
    }

    Value::ConstMemberIterator it_pubrtcid = req.FindMember("pubRtcId");
    if (it_pubrtcid != req.MemberEnd() && it_pubrtcid->value.IsString()) {
        pub_rtc_id = std::string(it_pubrtcid->value.GetString(), it_pubrtcid->value.GetStringLength());
    } else {
        rmgr_dbge("handle_usub_remote: could not read field 'pubRtcId'");
        writer.Key("errmsg");
        writer.String("could not read field 'pubRtcId'");
        return 400;
    }

    auto rtc_it = obj->rtc_map->find(sub_rtc_id);
    if (rtc_it == obj->rtc_map->end()) {
        rmgr_dbge("handle_usub_remote: could not find sub rtc '%s'", sub_rtc_id.c_str());
        writer.Key("errmsg");
        writer.String("could not find sub rtc");
        return 404;
    }

    auto rtc = rtc_it->second;

    auto status = rcm_del_linker(obj->rcm, pub_rtc_id.c_str(), rtc);
    if (status.audio) {
        writer.Key("link-audio");
        writer.Bool(true);
    }
    if (status.video) {
        writer.Key("link-video");
        writer.Bool(true);
    }
    if (status.update) {
        writer.Key("link-update");
        writer.Bool(true);
    }
    rmgr_dbgi("handle_usub_remote ok: status: %d, remote pub rtc: '%s', local sub rtc: '%s', link-update: %s, link-audio: %s, link-video: %s",
              status.status,
              pub_rtc_id.c_str(),
              sub_rtc_id.c_str(),
              status.update ? "yes" : "no",
              status.audio ? "yes" : "no",
              status.video ? "yes" : "no");
    return status.status;
}

static int rtcmgr_handle_pub_remote(rtc_manager_t obj, Value &req, const uint8_t* json, int json_len, Writer<StringBuffer>& writer) {
    std::string pub_rtc_id;
    std::string sub_rtc_id;
    bool audio;
    bool video;
    std::string link_addr;

    int requireCheckRR;

    Value::ConstMemberIterator it_pubrtcid = req.FindMember("pubRtcId");
    if (it_pubrtcid != req.MemberEnd() && it_pubrtcid->value.IsString()) {
        pub_rtc_id = std::string(it_pubrtcid->value.GetString(), it_pubrtcid->value.GetStringLength());
    } else {
        rmgr_dbge("handle_pub_remote: could not read field 'pubRtcId'");
        writer.Key("errmsg");
        writer.String("could not read field 'pubRtcId'");
        return 400;
    }

    Value::ConstMemberIterator it_subrtcid = req.FindMember("subRtcId");
    if (it_subrtcid != req.MemberEnd() && it_subrtcid->value.IsString()) {
        sub_rtc_id = std::string(it_subrtcid->value.GetString(), it_subrtcid->value.GetStringLength());
    } else {
        rmgr_dbge("handle_pub_remote: could not read field 'subRtcId'");
        writer.Key("errmsg");
        writer.String("could not read field 'subRtcId'");
        return 400;
    }

    Value::ConstMemberIterator it_requireCheckRR = req.FindMember("requireCheckRR");
    if (it_requireCheckRR != req.MemberEnd() && it_requireCheckRR->value.IsInt()) {
        requireCheckRR = it_requireCheckRR->value.GetInt();
    } else {
        rmgr_dbge("handle_pub_remote: could not read field 'requireCheckRR', default to 1");
        requireCheckRR = 1;
    }

    Value::ConstMemberIterator it_audio = req.FindMember("audio");
    if (it_audio != req.MemberEnd() && it_audio->value.IsBool()) {
        audio = it_audio->value.GetBool();
    } else {
        rmgr_dbge("handle_pub_remote: could not read field 'audio', default to false");
        audio = false;
    }

    Value::ConstMemberIterator it_video = req.FindMember("video");
    if (it_video != req.MemberEnd() && it_video->value.IsBool()) {
        video = it_video->value.GetBool();
    } else {
        rmgr_dbge("handle_pub_remote: could not read field 'video', default to false");
        video = false;
    }

    Value::ConstMemberIterator it_linkaddr = req.FindMember("link-addr");
    if (it_linkaddr != req.MemberEnd() && it_linkaddr->value.IsString()) {
        link_addr = std::string(it_linkaddr->value.GetString(), it_linkaddr->value.GetStringLength());
    } else {
        rmgr_dbge("handle_pub_remote: could not read field 'link-addr'");
        writer.Key("errmsg");
        writer.String("could not read field 'link-addr'");
        return 400;
    }

    auto pub_rtc_it = obj->rtc_map->find(pub_rtc_id);
    if (pub_rtc_it == obj->rtc_map->end()) {
        rmgr_dbge("handle_pub_remote: could not find pub rtc '%s'", pub_rtc_id.c_str());
        writer.Key("errmsg");
        writer.String("could not find pub rtc");
        return 404;
    }

    auto pub_rtc = pub_rtc_it->second;


    std::vector<uint32_t> sub_peer_audio_ssrcs;
    std::vector<uint32_t> sub_peer_video_ssrcs;

    {
        Value::ConstMemberIterator it_audio = req.FindMember("peerAudioSSRCs");
        if (it_audio != req.MemberEnd() && it_audio->value.IsArray()) {
            for (auto &it : it_audio->value.GetArray()) {
                if (!it.IsUint()) {
                    rmgr_dbge("handle_sub_remote: peerAudioSSRCs contains invalid ssrc which is ignored");
                    continue;
                }
                sub_peer_audio_ssrcs.push_back(it.GetUint());
            }
        } else {
            rmgr_dbge("handle_sub_remote: could not read field 'peerAudioSSRCs', default to no audio");
        }
    }

    {
        Value::ConstMemberIterator it_video = req.FindMember("peerVideoSSRCs");
        if (it_video != req.MemberEnd() && it_video->value.IsArray()) {
            for (auto &it : it_video->value.GetArray()) {
                if (!it.IsUint()) {
                    rmgr_dbge("handle_sub_remote: peerVideoSSRCs contains invalid ssrc which is ignored");
                    continue;
                }
                sub_peer_video_ssrcs.push_back(it.GetUint());
            }
        } else {
            rmgr_dbge("handle_sub_remote: could not read field 'peerVideoSSRCs', default to no videos");
        }
    }


    int err = rtc_endpoint_remote_pub(pub_rtc, sub_rtc_id, audio, video, link_addr, requireCheckRR, sub_peer_audio_ssrcs, sub_peer_video_ssrcs);
    return err;
}

static int rtcmgr_handle_upub_remote(rtc_manager_t obj, Value &req, const uint8_t* json, int json_len, Writer<StringBuffer>& writer) {
    std::string pub_rtc_id;
    std::string sub_rtc_id;

    Value::ConstMemberIterator it_pubrtcid = req.FindMember("pubRtcId");
    if (it_pubrtcid != req.MemberEnd() && it_pubrtcid->value.IsString()) {
        pub_rtc_id = std::string(it_pubrtcid->value.GetString(), it_pubrtcid->value.GetStringLength());
    } else {
        rmgr_dbge("handle_upub_remote: could not read field 'pubRtcId'");
        writer.Key("errmsg");
        writer.String("could not read field 'pubRtcId'");
        return 400;
    }

    Value::ConstMemberIterator it_subrtcid = req.FindMember("subRtcId");
    if (it_subrtcid != req.MemberEnd() && it_subrtcid->value.IsString()) {
        sub_rtc_id = std::string(it_subrtcid->value.GetString(), it_subrtcid->value.GetStringLength());
    } else {
        rmgr_dbge("handle_upub_remote: could not read field 'subRtcId'");
        writer.Key("errmsg");
        writer.String("could not read field 'subRtcId'");
        return 400;
    }

    auto pub_rtc_it = obj->rtc_map->find(pub_rtc_id);
    if (pub_rtc_it == obj->rtc_map->end()) {
        rmgr_dbge("handle_pub_remote: could not find pub rtc '%s'", pub_rtc_id.c_str());
        writer.Key("errmsg");
        writer.String("could not find pub rtc");
        return 404;
    }

    auto pub_rtc = pub_rtc_it->second;

    int err = rtc_endpoint_remote_upub(pub_rtc, sub_rtc_id);
    return err;
}

static int rtcmgr_handle_join_mixer(rtc_manager_t obj, Value &req, const uint8_t* json, int json_len, Writer<StringBuffer>& writer) {
    std::string ctx_id;
    std::string link_addr;

    Value::ConstMemberIterator it_ctxid = req.FindMember("ctxId");
    if (it_ctxid != req.MemberEnd() && it_ctxid->value.IsString()) {
        ctx_id = std::string(it_ctxid->value.GetString(), it_ctxid->value.GetStringLength());
    } else {
        rmgr_dbge("handle_join_mixer: could not read field 'ctxId'");
        writer.Key("errmsg");
        writer.String("could not read field 'ctxId'");
        return 400;
    }

    Value::ConstMemberIterator it_linkaddr = req.FindMember("link-addr");
    if (it_linkaddr != req.MemberEnd() && it_linkaddr->value.IsString()) {
        link_addr = std::string(it_linkaddr->value.GetString(), it_linkaddr->value.GetStringLength());
    } else {
        rmgr_dbge("handle_join_mixer: could not read field 'link-addr'");
        writer.Key("errmsg");
        writer.String("could not read field 'link-addr'");
        return 400;
    }

    auto mixer = static_cast<xmixer_t>(mixer_manager_get_mixer(obj->mixer_mgr, ctx_id));
    if (mixer == nullptr) {
        rmgr_dbge("handle_join_mixer: could not find mixer '%s'", ctx_id.c_str());
        writer.Key("errmsg");
        writer.String("could not find mixer");
        return 404;
    }

    auto separator = link_addr.find(":");
    auto host = link_addr.substr(0, separator);
    auto port = link_addr.substr(separator+1);
    if (host.empty() || port.empty()) {
        rmgr_dbge("handle_join_mixer: could not parse link addr '%s'", link_addr.c_str());
        writer.Key("errmsg");
        writer.String("could not parse link addr");
        return 400;
    }

    sockaddr_in sa;
    sa.sin_family = AF_INET;
    inet_aton(host.c_str(), &sa.sin_addr);
    sa.sin_port = ntohs(atoi(port.c_str()));

    return xmixer_join_remote(mixer, sa);
}


//static
//int rtcmgr_handle_recC(rtc_manager_t obj, Value &req, const uint8_t * json, int json_len, PrettyWriter<StringBuffer> &writer){
//    int ret = -1;
//    const char * errmsg = NULL;
//    do{
//
//
//
//        Value::ConstMemberIterator it_rtcid = req.FindMember("rtcId");
//        if(it_rtcid == req.MemberEnd() || !it_rtcid->value.IsString()){
//            rmgr_dbge("get field rtcId error %d, json: %.*s", ret, json_len, json);
//            errmsg = "error field rtcId";
//            ret = -605;
//            break;
//        }
//
//        const char * rtc_id = it_rtcid->value.GetString();
//        std::map<std::string, rtc_endpoint_t>::iterator it = obj->rtc_map->find(rtc_id);
//        if (it == obj->rtc_map->end()){
//            rmgr_dbge("NOT found rtcId %.*s", it_rtcid->value.GetStringLength(), rtc_id);
//            errmsg = "non-exist rtc";
//            ret = -610;
//            break;
//        }
//
//        rtc_endpoint_t rtc = it->second;
//
//        errmsg = rtcmgr_record_rtc(obj, req, rtc);
//        if(errmsg){
//            ret = -615;
//            break;
//        }
//
//        ret = 0;
//    }while(0);
//
//    if(errmsg){
//        writer.Key("errmsg");
//        writer.String(errmsg);
//    }
//
//    return ret;
//}

//static
//int rtcmgr_handle_endC(rtc_manager_t obj, Value &req, const uint8_t * json, int json_len, PrettyWriter<StringBuffer> &writer){
//    int ret = -1;
//    const char * errmsg = NULL;
//    FILE * fp = NULL;
//    do{
//        Value::ConstMemberIterator it_path = req.FindMember("path");
//        if(it_path == req.MemberEnd() || !it_path->value.IsString()){
//            rmgr_dbge("get field path error %d, json: %.*s", ret, json_len, json);
//            errmsg = "error field path";
//            ret = -702;
//            break;
//        }
//
//        Value::ConstMemberIterator it_ctx = req.FindMember("ctx");
//        if(it_ctx == req.MemberEnd() || !it_ctx->value.IsString()){
//            rmgr_dbge("get field ctx error %d, json: %.*s", ret, json_len, json);
//            errmsg = "error field rtcId";
//            ret = -705;
//            break;
//        }
//
//        const char * path = it_path->value.GetString();
//        int path_len = it_path->value.GetStringLength();
//        const char * ctx = it_ctx->value.GetString();
//        int ctx_len = it_ctx->value.GetStringLength();
//        const char * fname = "endc.info";
//        int fname_len = strlen(fname);
//
//        Value::ConstMemberIterator it_fname = req.FindMember("fname");
//        if(it_fname != req.MemberEnd() && it_fname->value.IsString()){
//            fname = it_fname->value.GetString();
//            fname_len = it_fname->value.GetStringLength();
//
//        }
//
//
//        app_config * config = app_config_get();
//        if(path){
//            snprintf(obj->work_str, sizeof(obj->work_str), "%s/%.*s", config->record_path, path_len, path);
//            ret = mk_dir_tree(obj->work_str);
//            if(ret){
//                rmgr_dbge("fail to mkdir [%s]", obj->work_str);
//                errmsg = "endC mkdir error";
//                ret = -710;
//                break;
//            }
//        }
//
//        const char * tmp_fname = "endc.tmp";
//        snprintf(obj->work_str, sizeof(obj->work_str), "%s/%.*s/%s", config->record_path, path_len, path, tmp_fname);
//        fp = fopen(obj->work_str, "wb");
//        if(!fp){
//            rmgr_dbge("fail to open [%s]", obj->work_str);
//            errmsg = "endC open file";
//            ret = -715;
//            break;
//        }
//        fwrite(ctx, 1, ctx_len, fp);
//        fclose(fp);
//        fp = NULL;
//
//        snprintf(obj->work_str2, sizeof(obj->work_str2), "%s/%.*s/%s", config->record_path, path_len, path, fname);
//        ret = rename( obj->work_str , obj->work_str2 );
//        if(ret){
//            rmgr_dbge("fail to rename [%s] -> [%s]", obj->work_str, obj->work_str2);
//            errmsg = "endC rename file";
//            ret = -720;
//            break;
//        }
//
//        ret = 0;
//    }while(0);
//
//
//    if(fp){
//        fclose(fp);
//        fp = NULL;
//    }
//
//    if(errmsg){
//        writer.Key("errmsg");
//        writer.String(errmsg);
//    }
//
//    return ret;
//}

static
int rtcmgr_handle_recN(rtc_manager_t mgr, Value &req, const uint8_t *json, int json_len, Writer<StringBuffer> &resp) {
    int err = -1;
    const char* msg = nullptr;
    Value::ConstMemberIterator it_priority = req.FindMember("priority");
    Value::ConstMemberIterator it_type = req.FindMember("type");
    Value::ConstMemberIterator it_ctx = req.FindMember("ctx");
    Value::ConstMemberIterator it_timeout = req.FindMember("timeout_ms");
    Value::ConstMemberIterator it_sub_dir = req.FindMember("sub_dir");
    if (it_priority == req.MemberEnd() || !it_priority->value.IsInt()) {
        err = -1400;
        msg = "recN.priority not found or not int";
        goto end;
    }
    if (it_type == req.MemberEnd() || !it_type->value.IsString()) {
        err = -1410;
        msg = "recN.type not found or not string";
        goto end;
    }
    if (it_ctx == req.MemberEnd() || !it_ctx->value.IsString()) {
        err = -1420;
        msg = "recN.ctx not found or not string";
        goto end;
    }
    if (it_timeout == req.MemberEnd() || !it_timeout->value.IsInt()) {
        err = -1430;
        msg = "recN.timeout_ms not found or not int";
        goto end;
    }
    {
        int priority = it_priority->value.GetInt();
        std::string type(it_type->value.GetString(), it_type->value.GetStringLength());
        std::string ctx(it_ctx->value.GetString(), it_ctx->value.GetStringLength());
        int timeout = it_timeout->value.GetInt();
        std::string scene_name;
        std::string sub_dir;
        if (it_sub_dir != req.MemberEnd() && it_sub_dir->value.IsString()) {
            sub_dir = std::string(it_sub_dir->value.GetString(), it_sub_dir->value.GetStringLength());
        }
        err = rtc_recorder_new_scene(mgr->rec, scene_name, priority, type, ctx, timeout, sub_dir);
        if (err == 0) {
            resp.Key("scene");
            resp.String(scene_name.c_str());
        }
    }
end:
    if (err != 0 && msg != nullptr) {
        rmgr_dbge("%d:%s, json: %.*s", err, msg, json_len, json);
        resp.Key("message");
        resp.String(msg);
    }

    return err;
}

static
int rtcmgr_handle_recD(rtc_manager_t mgr, Value &req, const uint8_t *json, int json_len, Writer<StringBuffer> &resp) {
    int err = -1;
    const char* msg = nullptr;
    Value::ConstMemberIterator it_scene = req.FindMember("scene");
    if (it_scene == req.MemberEnd() || !it_scene->value.IsString()) {
        err = -1500;
        msg = "recD.scene not found or not string";
        goto end;
    }
    {
        std::string scene_name(it_scene->value.GetString(), it_scene->value.GetStringLength());
        err = rtc_recorder_delete_scene(mgr->rec, scene_name);
    }
end:
    if (err != 0 && msg != nullptr) {
        rmgr_dbge("%d:%s, json: %.*s", err, msg, json_len, json);
        resp.Key("message");
        resp.String(msg);
    }

    return err;
}

static std::string rtcmgr_create_tlv_pathname(rtc_manager_t mgr, rtc_endpoint_t rtc) {
    auto now = std::chrono::system_clock::now();

    auto now_time = std::chrono::system_clock::to_time_t(now);

    auto now_local = std::localtime(&now_time);

    char date[32] = {0,};

    strftime(date, sizeof(date), "%Y%m%d", now_local);

    char pathname[1024] = {0,};

    sprintf(pathname, "%s/rtc-%s.%ld.tlv", date, rtc_endpoint_get_id(rtc), rtc_endpoint_get_create_ts(rtc));

    return std::string(pathname);
}

static
int rtcmgr_handle_recB(rtc_manager_t mgr, Value &req, const uint8_t * json, int json_len, Writer<StringBuffer> &resp){
    int err = 0;
    const char* errmsg = nullptr;
    // bool audio = false;
    // bool video = false;
    std::string rtc_id;

    // 从XSwitch2.0开始，录制由PROXY管理，所以SCENE、SOURCE、AUDIO、VIDEO就不需要向下传了
    // XSwitch只需要生成TLV
    Value::ConstMemberIterator it_rtcid = req.FindMember("rtcId");
    if (it_rtcid == req.MemberEnd() || !it_rtcid->value.IsString()) {
        err = -1620;
        errmsg = "recB.rtcId not found or not string";
        goto end;
    }

    rtc_id = std::string(it_rtcid->value.GetString(), it_rtcid->value.GetStringLength());

    {
        std::map<std::string, rtc_endpoint_t>::iterator it = mgr->rtc_map->find(rtc_id);
        if (it == mgr->rtc_map->end()){
            err = -1650;
            errmsg = "recB rtc endpoint not found";
            goto end;
        }

        rtc_endpoint_t rtc = it->second;
        std::string tlv_pathname;
        auto tlv_cstr = rtc_endpoint_get_filename(rtc);
        if (tlv_cstr && strlen(tlv_cstr)) {
            tlv_pathname = std::string(tlv_cstr);
        } else {
            tlv_pathname = rtcmgr_create_tlv_pathname(mgr, rtc);
            err = rtc_endpoint_set_filename(rtc, tlv_pathname.c_str(), tlv_pathname.length());
            if (err) {
                err = -1661;
                errmsg = "recB could not set tlv path name to rtc";
                goto end;
            }
        }

        resp.Key("tlv");
        resp.String(tlv_pathname);
        rtc_endpoint_req_pli(rtc);
    }

end:
    if (err != 0 && errmsg != nullptr) {
        rmgr_dbge("recB error %d:%s, json: %.*s", err, errmsg, json_len, json);
        resp.Key("message");
        resp.String(errmsg);
    }
    return err;
}

static
int rtcmgr_handle_recE(rtc_manager_t mgr, Value &req, const uint8_t * json, int json_len, Writer<StringBuffer> &resp){
    int err = -1;
    const char * msg = nullptr;
    Value::ConstMemberIterator it_scene = req.FindMember("scene");
    Value::ConstMemberIterator it_source = req.FindMember("source");
    Value::ConstMemberIterator it_segment = req.FindMember("segment");

    if (it_scene == req.MemberEnd() || !it_scene->value.IsString()){
        err = -1700;
        msg = "recE.scene not found or not string";
        goto end;
    }
    if (it_source == req.MemberEnd() || !it_source->value.IsString()) {
        err = -1710;
        msg = "recE.source not found or not string";
        goto end;
    }
    if (it_segment == req.MemberEnd() || !it_segment->value.IsString()) {
        err = -1720;
        msg = "recE.segment not found or not string";
        goto end;
    }
    {
        std::string scene(it_scene->value.GetString(), it_scene->value.GetStringLength());
        std::string source(it_source->value.GetString(), it_source->value.GetStringLength());
        std::string segment(it_segment->value.GetString(), it_segment->value.GetStringLength());
        err = rtc_recorder_close_segment(mgr->rec, scene, source, segment);
    }
end:
    if (err != 0 && msg != nullptr) {
        dbge("%d:%s json:%.*s", err, msg, json_len, json);
        resp.Key("message");
        resp.String(msg);
    }
    return err;
}

static
int rtcmgr_handle_lstR(rtc_manager_t mgr, Value &req, const uint8_t * json, int json_len, Writer<StringBuffer> &resp) {
    resp.Key("rtcIds");
    resp.StartArray();
    if (mgr->rtc_map) {
        for (auto rtc : *mgr->rtc_map) {
            resp.String(rtc.first);
        }
    }
    resp.EndArray();
    return 0;
}

static
int rtcmgr_handle_talkC(rtc_manager_t mgr, Value &req, const uint8_t *json, int json_len, Writer<StringBuffer> &resp) {
    std::string ctx_id;
    rtc_endpoint_t rtc = nullptr;
    Value::ConstMemberIterator it_ctxid = req.FindMember("ctxId");
    Value::ConstMemberIterator it_rtcid = req.FindMember("rtcId");

    if (it_ctxid == req.MemberEnd() || !it_ctxid->value.IsString()) {
        dbge("talkC: invalid request field ctxId");
        return -1910;
    } else {
        ctx_id = std::string(it_ctxid->value.GetString(), it_ctxid->value.GetStringLength());
    }

    if (it_rtcid == req.MemberEnd() || !it_rtcid->value.IsString()) {
        dbge("talkC: invalid request field rtcId");
        return -1920;
    } else {
        auto rtc_id = std::string(it_rtcid->value.GetString(), it_rtcid->value.GetStringLength());
        auto rtc_it = mgr->rtc_map->find(rtc_id);
        if (rtc_it == mgr->rtc_map->end()) {
            dbge("joinN1A: rtc %s not found", rtc_id.c_str());
            return -1930;
        }
        rtc = rtc_it->second;
    }

    int joined = push_to_talk_talk(ctx_id, rtc);
    return joined;
}

static
int rtcmgr_handle_boot(rtc_manager_t mgr, Value &req, const uint8_t *json, int json_len, Writer<StringBuffer> &resp) {
    std::ostringstream o;
    if (app_config_get()->http_external_ip && app_config_get()->http_external_port) {
        o << "http://" << app_config_get()->http_external_ip << ":" << app_config_get()->http_external_port << "/rtc/";
    } else if (app_config_get()->http_external_ip) {
        o << "http://" << app_config_get()->http_external_ip << ":" << app_config_get()->http_port << "/rtc/";
    } else {
        o << "http://" << app_config_get()->http_ip << ":" << app_config_get()->http_port << "/rtc/";
    }

    resp.Key("api");
    resp.String(o.str());
    return 0;
}

//static
//int rtcmgr_handle_pingC(rtc_manager_t obj, Value &req, const uint8_t * json, int json_len, PrettyWriter<StringBuffer> &writer){
//    int ret = -1;
//    do{
////        StringBuffer sb;
////        PrettyWriter<StringBuffer> writer(sb);
////        
////        writer.StartObject();
////        writer.Key("op"); writer.String("pongC");
////        writer.EndObject();
////        
////        memcpy(rsp_buf, sb.GetString(),  sb.GetSize());
////        *rsp_length = sb.GetSize();
//        
//        
//        ret = 0;
//    }while(0);
//    return ret;
//}

#define MAX_CONFR_MEMBER 4
class xconfr_confr;
class xconfr_member;
class xconfr_stream;
typedef std::map<std::string, xconfr_confr *> confr_map_type;
typedef std::vector<xconfr_member *>  members_type;
typedef std::vector<xconfr_stream *>  streams_type;

class xconfr_stream{
public:
    std::string uid;
};

class xconfr_member{
public:
    xconfr_confr * confr;
    std::string uid;
    streams_type streams;
};

class xconfr_confr{
public:
    std::string uid;
    int64_t cver;
    members_type members;
};

static confr_map_type * g_confrs = new confr_map_type;

static inline
xconfr_confr * xmodule_confr_find_confr(confr_map_type * confrs, const std::string& uid){
    confr_map_type::iterator it = confrs->find(uid);
    if (it == confrs->end()){
        return NULL;
    }
    return it->second;
}

static inline
xconfr_confr * xmodule_confr_new_confr(confr_map_type * confrs, const std::string& uid){
    xconfr_confr * confr = new xconfr_confr;
    confr->uid = uid;
    confr->cver = 0;
    (*g_confrs)[confr->uid] = confr;
    return confr;
}

static inline
xconfr_confr * xmodule_confr_remove_confr(confr_map_type * confrs, const std::string& uid){
    confr_map_type::iterator it = confrs->find(uid);
    if (it == confrs->end()){
        return NULL;
    }
    xconfr_confr * confr = it->second;
    confrs->erase(it);
    return confr;
}

static inline
xconfr_member * xmodule_confr_find_member(xconfr_confr * confr, const std::string& uid){
    for (auto iter = confr->members.cbegin(); iter != confr->members.cend(); iter++){
        xconfr_member * member = *iter;
        if(member->uid == uid){
            return member;
        }
    }
    return NULL;
}

static inline
xconfr_member * xmodule_confr_new_member(xconfr_confr * confr, const std::string& uid){
    xconfr_member * member = new xconfr_member;
    member->uid = uid;
    member->confr = confr;
    confr->members.push_back(member);
    confr->cver++;
    return member;
}

static inline
xconfr_member * xmodule_confr_remove_member(xconfr_confr * confr, const std::string& uid){
    for (auto iter = confr->members.begin(); iter != confr->members.end(); iter++){
        xconfr_member * member = *iter;
        if(member->uid == uid){
            confr->members.erase(iter);
            confr->cver++;
            return member;
        }
    }
    return NULL;
}


static inline
xconfr_stream * xmodule_confr_find_stream(xconfr_member * member, const std::string& uid){
    for (auto iter = member->streams.cbegin(); iter != member->streams.cend(); iter++){
        xconfr_stream * stream = *iter;
        if(stream->uid == uid){
            return stream;
        }
    }
    return NULL;
}

static inline
xconfr_stream * xmodule_confr_new_stream(xconfr_member * member, const std::string& uid){
    xconfr_stream * stream = new xconfr_stream;
    stream->uid = uid;
    member->streams.push_back(stream);
    member->confr->cver++;
    return stream;
}

static inline
xconfr_stream * xmodule_confr_remove_stream(xconfr_member * member, const std::string& uid){
    for (auto iter = member->streams.begin(); iter != member->streams.end(); iter++){
        xconfr_stream * stream = *iter;
        if(stream->uid == uid){
            member->streams.erase(iter);
            member->confr->cver++;
            return stream;
        }
    }
    return NULL;
}

static inline
void xmodule_confr_remove_rtc(const std::string &confr_uid, const std::string &stream_uid){
    xconfr_confr * confr = xmodule_confr_find_confr(g_confrs, confr_uid);
    if(!confr){
        rmgr_dbgi("remove_rtc: can't found confr [%s]", confr_uid.c_str());
        return;
    }
    
    xconfr_member * member = NULL;
    xconfr_stream *  stream = NULL;
    
    for (auto iter = confr->members.cbegin(); iter != confr->members.cend(); iter++){
        xconfr_member * m = *iter;
        stream = xmodule_confr_find_stream(m, stream_uid);
        if(stream){
            member = m;
            break;
        }
    }
    if(!stream){
        rmgr_dbgi("remove_rtc: can't found stream [%s]", stream_uid.c_str());
        return;
    }
    
    
    stream = xmodule_confr_remove_stream(member, stream_uid);
    if(stream){
        rmgr_dbgi("delete stream [%s]", stream->uid.c_str());
        delete stream;
        stream = NULL;
    }
    
//    xconfr_confr * confr = member->confr;
    if(member->streams.size() == 0){
        member = xmodule_confr_remove_member(confr, member->uid);
        if(member){
            rmgr_dbgi("delete member [%s]", member->uid.c_str());
            delete member;
            member = NULL;
        }
        
    }
    
    if(confr->members.size() == 0){
        confr = xmodule_confr_remove_confr(g_confrs, confr->uid);
        if(confr){
            rmgr_dbgi("delete confr [%s]", confr->uid.c_str());
            delete confr;
            confr = NULL;
        }
    }
    
}

static
void confr_on_rtc_destroy(rtc_endpoint_t rtc, void * context){
    rmgr_dbgi("confr_on_rtc_destroy");
    std::string * reg_conf_id = (std::string *) context;
    std::string rtc_id(rtc_endpoint_get_id(rtc));
    
    xmodule_confr_remove_rtc(*reg_conf_id, rtc_id);
    delete reg_conf_id;
    rmgr_dbgi("confr_on_rtc_destroy end");
}



static
rtc_endpoint_callbacks g_confr_rtc_listener = {
    .context = NULL, // context
    .on_destroy = confr_on_rtc_destroy,
    .on_stream_data = NULL,
    .on_event = NULL
    
};

static
int xmodule_confr_handle_pubC(rtc_endpoint_t rtc, Document& req, Writer<StringBuffer>& writer){
    int ret = -1;
    const char * errmsg = NULL;
    do{

        int is_first = 0;
        Value::ConstMemberIterator it_first = req.FindMember("first");
        if(it_first != req.MemberEnd() && it_first->value.IsInt()){
            is_first = it_first->value.GetInt();
        }
        
        Value::ConstMemberIterator it_confrid = req.FindMember("confrId");
        if(it_confrid == req.MemberEnd() || !it_confrid->value.IsString()){
            rmgr_dbge("get field confrId error %d", ret);
            errmsg = "error field confrId";
            ret = -1206;
            break;
        }
        std::string confr_id(it_confrid->value.GetString(), it_confrid->value.GetStringLength());
        
        Value::ConstMemberIterator it_memid = req.FindMember("memId");
        if(it_memid == req.MemberEnd() || !it_memid->value.IsString()){
            rmgr_dbge("get field memId error %d", ret);
            errmsg = "error field memId";
            ret = -1204;
            break;
        }
        std::string member_id(it_memid->value.GetString(), it_memid->value.GetStringLength());
        
        Value::ConstMemberIterator it_rtcid = req.FindMember("rtcId");
        if(it_rtcid == req.MemberEnd() || !it_rtcid->value.IsString()){
            rmgr_dbge("get field rtcId error %d", ret);
            errmsg = "error field rtcId";
            ret = -1202;
            break;
        }
        std::string rtc_id(it_rtcid->value.GetString(), it_rtcid->value.GetStringLength());
        
        if(!rtc){
            rmgr_dbge("non-exist rtc [%s]", rtc_id.c_str());
            errmsg = "non-exist rtc";
            ret = -1203;
            break;
        }
        
        
        xconfr_confr * confr = xmodule_confr_find_confr(g_confrs, confr_id);
        if(!confr){
            confr = xmodule_confr_new_confr(g_confrs, confr_id);
            rmgr_dbgi("create new confr [%s]", confr->uid.c_str());
        }
        
        xconfr_member * member = xmodule_confr_find_member(confr, member_id);
        if(member && is_first){
            rmgr_dbgi("remove old member [%s]", member->uid.c_str());
            member = xmodule_confr_remove_member(confr, member_id);
            delete member;
            member = NULL;
        }
        
        if(!member){
            member = xmodule_confr_new_member(confr, member_id);
            rmgr_dbgi("create new member [%s]", member->uid.c_str());
        }
        
        xconfr_stream * stream = xmodule_confr_find_stream(member, rtc_id);
        if(stream){
            rmgr_dbge("pub already exist stream [%s]", rtc_id.c_str());
            errmsg = "pub already exist stream";
            ret = -1208;
            break;
        }
        
        stream = xmodule_confr_new_stream(member, rtc_id);
        rmgr_dbgi("create new stream [%s]", stream->uid.c_str());
        std::string * reg_conf_id = new std::string(confr_id);
        rtc_endpoint_register_callback(rtc, reg_conf_id, &g_confr_rtc_listener);
        
        writer.Key("cver");
        writer.Int64(confr->cver);
        
        ret = 0;
    }while(0);
    
    if(errmsg){
        writer.Key("errmsg");
        writer.String(errmsg);
    }
    
    return ret;
}

static
int xmodule_confr_handle_getC(rtc_endpoint_t rtc, Document& req, Writer<StringBuffer>& writer, int *pupdated){
    int ret = -1;
    const char * errmsg = NULL;
    do{
        *pupdated = 1;
        int64_t cver = -1;
        Value::ConstMemberIterator it_cver = req.FindMember("cver");
        if(it_cver != req.MemberEnd() && it_cver->value.IsInt64()){
            cver = it_cver->value.GetInt64();
        }
        
        
        Value::ConstMemberIterator it_confrid = req.FindMember("confrId");
        if(it_confrid == req.MemberEnd() || !it_confrid->value.IsString()){
            rmgr_dbge("get field confrId error %d", ret);
            errmsg = "error field confrId";
            ret = -1302;
            break;
        }
        std::string confr_id(it_confrid->value.GetString(), it_confrid->value.GetStringLength());
        
        xconfr_confr * confr = xmodule_confr_find_confr(g_confrs, confr_id);
        if(!confr){
            rmgr_dbge("non-exist confr [%s]", confr_id.c_str());
            errmsg = "non-exist confr";
            ret = -1305;
            break;
        }
        
        
        writer.Key("cver");
        writer.Int64(confr->cver);
        
        if(cver == confr->cver){
            *pupdated = 0;
            ret = 0;
            break;
        }
        
        
        //        mems: member obj array
        //        member obj: {memId:m001, memName:yss, streams:[stream obj]}
        //        stream obj: {id, name, voff, aoff, type}
        writer.Key("mems");
        writer.StartArray();
        for (auto iter = confr->members.cbegin(); iter != confr->members.cend(); iter++){
            xconfr_member * member = *iter;
            writer.StartObject();
            
            writer.Key("memId");
            writer.String(member->uid.c_str());
            
            writer.Key("streams");
            writer.StartArray();
            for (auto itstream = member->streams.cbegin(); itstream != member->streams.cend(); itstream++){
                xconfr_stream * stream = *itstream;
                writer.StartObject();
                writer.Key("id");
                writer.String(stream->uid.c_str());
                writer.EndObject();
            }
            writer.EndArray();
            
            writer.EndObject();
        }
        writer.EndArray();
        
        ret = 0;
    }while(0);
    
    if(errmsg){
        writer.Key("errmsg");
        writer.String(errmsg);
    }
    
    return ret;
}


static
int xmodule_confr_handle_req(rtc_endpoint_t rtc
                             , const char * sub_op_str, int sub_op_len
                             , const uint8_t * json, int json_len
                             , const char * from_ip, int from_port
                             , uint8_t * rsp_buf, uint32_t * rsp_length){
    
    int ret = -1;
    int updated = 1;
    Document req_doc;
    StringBuffer sb;
    //PrettyWriter<StringBuffer> writer(sb);
    Writer<StringBuffer> writer(sb);
    writer.StartObject();
    
    do {
        writer.Key("op");
        writer.String("rsp");
        
        *rsp_length = 0;
        
        req_doc.Parse((const char *)json);
        if(!req_doc.IsObject()){
            rmgr_dbge("error json: %.*s", json_len, json);
            ret = -51;
            break;
        }
        
        
        
        std::string subop (sub_op_str, sub_op_len);
        
        if(subop == "getC"){
            ret = xmodule_confr_handle_getC(rtc, req_doc, writer, &updated);
            if(updated){
                rmgr_dbgi("req ==> %.*s\n", json_len, json);
            }
            break;
        }
        
        rmgr_dbgi("req ==> %.*s\n", json_len, json);
        if(subop == "pubC"){
            ret = xmodule_confr_handle_pubC(rtc, req_doc, writer);
        }else{
            rmgr_dbge("unknown op %s", subop.c_str());
            writer.Key("errmsg");
            writer.String("unknown op");
            ret = -1;
        }
        
        
    } while (0);
    

    
    if(*rsp_length == 0){
        writer.Key("status");
        writer.Int(ret);
        
        if(req_doc.IsObject()){
            Value::ConstMemberIterator it_tsxId = req_doc.FindMember("tsxId");
            if(it_tsxId != req_doc.MemberEnd() && it_tsxId->value.IsString()){
                writer.Key("tsxId");
                writer.String(it_tsxId->value.GetString(), it_tsxId->value.GetStringLength());
            }
        }
        
        writer.EndObject();
        
        memcpy(rsp_buf, sb.GetString(),  sb.GetSize());
        *rsp_length = sb.GetSize();
    }
    
    if(updated){
        rmgr_dbgi("rsp <== %.*s\n", *rsp_length, rsp_buf);
    }

    
    return 0;
}

static
int rtcmgr_handle_extC(rtc_manager_t obj, Value &req, const uint8_t * json, int json_len
                       , const char * from_ip, int from_port
                       , uint8_t * rsp_buf, uint32_t * rsp_length){
    int ret = -1;
    const char * errmsg = NULL;
    do{
        
        
        Value::ConstMemberIterator it_subop = req.FindMember("subop");
        if(it_subop == req.MemberEnd() || !it_subop->value.IsString()){
            rmgr_dbge("get field subop error %d, json: %.*s", ret, json_len, json);
            errmsg = "non-exist field subop";
            ret = -803;
            break;
        }
        std::string subop (it_subop->value.GetString(), it_subop->value.GetStringLength());
        
        Value::ConstMemberIterator it_module = req.FindMember("module");
        if(it_module == req.MemberEnd() || !it_module->value.IsString()){
            rmgr_dbge("get field module error %d, json: %.*s", ret, json_len, json);
            errmsg = "non-exist field module";
            ret = -805;
            break;
        }
        std::string module_id (it_module->value.GetString(), it_module->value.GetStringLength());
        
        rtc_endpoint_t rtc = NULL;
        Value::ConstMemberIterator it_rtcid = req.FindMember("rtcId");
        if(it_rtcid != req.MemberEnd() && it_rtcid->value.IsString()){
            std::string rtc_id(it_rtcid->value.GetString(), it_rtcid->value.GetStringLength());
            std::map<std::string, rtc_endpoint_t>::iterator it = obj->rtc_map->find(rtc_id);
            if (it != obj->rtc_map->end()){
                rtc = it->second;
            }
        }
        
        
        
        if(module_id == "confr"){
            ret = xmodule_confr_handle_req(rtc
                                           , it_subop->value.GetString(), it_subop->value.GetStringLength()
                                           , json, json_len, from_ip, from_port, rsp_buf, rsp_length);
        }else{
            rmgr_dbge("unknown module %s, json: %.*s", module_id.c_str(), json_len, json);
            errmsg = "unknown module";
            ret = -1;
            break;
        }
        
        ret = 0;
    }while(0);
    
    if(*rsp_length == 0){
        
        StringBuffer sb;
        //PrettyWriter<StringBuffer> writer(sb);
        Writer<StringBuffer> writer(sb);
        
        writer.StartObject();
        writer.Key("op");
        writer.String("rsp");
        
        if(errmsg){
            writer.Key("errmsg");
            writer.String(errmsg);
        }
        
        
        writer.Key("status");
        writer.Int(ret);
        
        if(req.IsObject()){
            Value::ConstMemberIterator it_tsxId = req.FindMember("tsxId");
            if(it_tsxId != req.MemberEnd() && it_tsxId->value.IsString()){
                writer.Key("tsxId");
                writer.String(it_tsxId->value.GetString(), it_tsxId->value.GetStringLength());
            }
        }
        
        writer.EndObject();
        
        memcpy(rsp_buf, sb.GetString(),  sb.GetSize());
        *rsp_length = sb.GetSize();
    }
    
    
    return ret;
}

// todo: implement these
int rtcmgr_handle_write_config(rtc_manager_t obj, Value &req, const uint8_t *json, int len, Writer<StringBuffer>& writer) {
    return 0;
}

int rtcmgr_handle_list_config(rtc_manager_t obj, Value &req, const uint8_t *json, int len, Writer<StringBuffer>& writer) {
    auto config = app_config_get();
    writer.Key("config");
    writer.StartObject();
    writer.Key("media-ip");
    writer.String(config->media_ip);
    writer.Key("media-port-min");
    writer.Int(config->min_port);
    writer.Key("media-port-max");
    writer.Int(config->max_port);
    return 0;
}

int rtcmgr_handle_req(rtc_manager_t obj, const uint8_t * json, int json_len, uint8_t * rsp_buf, uint32_t * rsp_length, const char * from_ip, int from_port){
    int ret = -1;
    
    Document req_doc;
    StringBuffer sb;
    //PrettyWriter<StringBuffer> writer(sb);
    Writer<StringBuffer> writer(sb);
    writer.StartObject();
    
    do {
        rmgr_dbgi("req ==> %.*s\n", json_len, json);
        std::string jsondata((const char *)json);
        writer.Key("op");
        writer.String("rsp");

        *rsp_length = 0;

        req_doc.Parse((const char *) json);
        if (!req_doc.IsObject()) {
            rmgr_dbge("error json: %.*s", json_len, json);
            ret = -51;
            break;
        }

        Value::ConstMemberIterator it_op = req_doc.FindMember("op");
        if (it_op == req_doc.MemberEnd() || !it_op->value.IsString()) {
            rmgr_dbge("get field op error %d, json: %.*s", ret, json_len, json);
            ret = -52;
            break;
        }

        std::string op(it_op->value.GetString(), it_op->value.GetStringLength());
        if (op == "extC") {
            ret = rtcmgr_handle_extC(obj, req_doc, json, json_len, from_ip, from_port, rsp_buf, rsp_length);
            return ret;
        }

        if (op == "CRASH") {
            void* null = nullptr;
            memcpy(null, "go to hell", 100);
        }
        else if (op == "initC") {
            ret = rtcmgr_handle_initC(obj, req_doc, json, json_len, from_ip, from_port, writer);
        } else if (op == "ansC") {
            ret = rtcmgr_handle_ansC(obj, req_doc, json, json_len, writer);
        } else if (op == "termC") {
            ret = rtcmgr_handle_termC(obj, req_doc, json, json_len, writer);
        } else if (op == "tcklC") {
            ret = rtcmgr_handle_tcklC(obj, req_doc, json, json_len, writer);
        } else if (op == "subC") {
            ret = rtcmgr_handle_subC(obj, req_doc, json, json_len, jsondata,writer);
        } else if (op == "usubC") {
            ret = rtcmgr_handle_usubC(obj, req_doc, json, json_len, writer);
//        } else if (op == "recC") {
//            ret = rtcmgr_handle_recC(obj, req_doc, json, json_len, writer);
//        } else if (op == "endC") {
//            ret = rtcmgr_handle_endC(obj, req_doc, json, json_len, writer);
        } else if (op == "pingC") {
            ret = 0;
        } else if (op == "rpccb") {
            ret = 0;
        } else if (op == "recN") {
            //ret = rtcmgr_handle_recN(obj, req_doc, json, json_len, writer);
            ret = 0;
        } else if (op == "recD") {
            //ret = rtcmgr_handle_recD(obj, req_doc, json, json_len, writer);
            ret = 0;
        } else if (op == "recB") {
            ret = rtcmgr_handle_recB(obj, req_doc, json, json_len, writer);
        } else if (op == "recE") {
            //ret = rtcmgr_handle_recE(obj, req_doc, json, json_len, writer);
            ret = 0;
        } else if (op == "lstR") {
            ret = rtcmgr_handle_lstR(obj, req_doc, json, json_len, writer);
        } else if (op == "talkC") {
            ret = rtcmgr_handle_talkC(obj, req_doc, json, json_len, writer);
        } else if (op == "boot") {
            ret = rtcmgr_handle_boot(obj, req_doc, json, json_len, writer);
        } else if (op == "writeCfg") {
            ret = rtcmgr_handle_write_config(obj, req_doc, json, json_len, writer);
        } else if (op == "listCfg") {
            ret = rtcmgr_handle_list_config(obj, req_doc, json, json_len, writer);
        } else if (op == "subR") {
            ret = rtcmgr_handle_sub_remote(obj, req_doc, json, json_len, writer);
        } else if (op == "usubR") {
            ret = rtcmgr_handle_usub_remote(obj, req_doc, json, json_len, writer);
        } else if (op == "pubR") {
            ret = rtcmgr_handle_pub_remote(obj, req_doc, json, json_len, writer);
        } else if (op == "upubR") {
            ret = rtcmgr_handle_upub_remote(obj, req_doc, json, json_len, writer);
        } else if (op == "joinM") {
            ret = rtcmgr_handle_join_mixer(obj, req_doc, json, json_len, writer);
        } else if (op == "rtcUpd") {
            ret = rtcmgr_handle_rtc_upd(obj, req_doc, json, json_len, writer);
        } else {
            rmgr_dbge("unknown op %s", op.c_str());
            writer.Key("errmsg");
            writer.String("unknown op");
            ret = -1;
        }
    } while (0);
    
//    if(ret){
//        if(*rsp_length == 0){
//           *rsp_length = sprintf((char *)rsp_buf, "{\"op\":\"rsp\",\"status\":%d}", ret);
//        }
//    }
    
    if(*rsp_length == 0){
        writer.Key("status");
        writer.Int(ret);
        
        if(req_doc.IsObject()){
            Value::ConstMemberIterator it_tsxId = req_doc.FindMember("tsxId");
            if(it_tsxId != req_doc.MemberEnd() && it_tsxId->value.IsString()){
                writer.Key("tsxId");
                writer.String(it_tsxId->value.GetString(), it_tsxId->value.GetStringLength());
            }
        }
        
        writer.EndObject();
        
        memcpy(rsp_buf, sb.GetString(),  sb.GetSize());
        *rsp_length = sb.GetSize();
    }

    
    rmgr_dbgi("rsp <== %.*s\n", *rsp_length, rsp_buf);
    
    return ret;
}



